From 365d259e944f1b5467178d527180b1a336a63255 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani <46041660+null2264@users.noreply.github.com> Date: Mon, 23 Dec 2024 07:07:11 +0700 Subject: [PATCH 001/181] refactor(library): Utilise flow even more (#272) * revert: "revert: "refactor(library): Some adjustments"" This reverts commit 2b639d063069e57d59df4aebeca89eabd902697f. * fix: Don't use emptyFlow * fix: Fix build * fix: Don't overwrite allCategories inside `combine {}` * fix: Fix build --- .../tachiyomi/ui/library/LibraryController.kt | 20 +-- .../tachiyomi/ui/library/LibraryPresenter.kt | 128 ++++++++++-------- .../ui/library/display/LibraryCategoryView.kt | 10 +- .../ui/library/filter/FilterBottomSheet.kt | 6 +- 4 files changed, 87 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 2493614196..bf10138a82 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -557,7 +557,7 @@ open class LibraryController( } presenter.groupType = item shouldScrollToTop = true - presenter.getLibrary() + presenter.updateLibrary() true }.show() } @@ -1056,7 +1056,7 @@ open class LibraryController( if (type.isEnter) { binding.filterBottomSheet.filterBottomSheet.isVisible = true if (type == ControllerChangeType.POP_ENTER) { - presenter.getLibrary() + presenter.updateLibrary() isPoppingIn = true } binding.recyclerCover.isClickable = false @@ -1095,7 +1095,7 @@ open class LibraryController( if (!isBindingInitialized) return updateFilterSheetY() if (observeLater) { - presenter.getLibrary() + presenter.updateLibrary() } } @@ -1408,7 +1408,7 @@ open class LibraryController( private fun onRefresh() { showCategories(false) - presenter.getLibrary() + presenter.updateLibrary() destroyActionModeIfNeeded() } @@ -1432,14 +1432,14 @@ open class LibraryController( val isShowAllCategoriesSet = preferences.showAllCategories().get() if (!query.isNullOrBlank() && this.query.isBlank() && !isShowAllCategoriesSet) { presenter.forceShowAllCategories = preferences.showAllCategoriesWhenSearchingSingleCategory().get() - presenter.getLibrary() + presenter.updateLibrary() } else if (query.isNullOrBlank() && this.query.isNotBlank() && !isShowAllCategoriesSet) { if (!isSubClass) { preferences.showAllCategoriesWhenSearchingSingleCategory() .set(presenter.forceShowAllCategories) } presenter.forceShowAllCategories = false - presenter.getLibrary() + presenter.updateLibrary() } if (query != this.query && !query.isNullOrBlank()) { @@ -1641,7 +1641,7 @@ open class LibraryController( if (mangaId == null) { adapter.getHeaderPositions().forEach { adapter.notifyItemChanged(it) } } else { - presenter.updateManga() + presenter.updateLibrary() } } @@ -1820,7 +1820,7 @@ open class LibraryController( val category = (adapter.getItem(position) as? LibraryHeaderItem)?.category ?: return if (!category.isDynamic) { ManageCategoryDialog(category) { - presenter.getLibrary() + presenter.updateLibrary() }.showDialog(router) } } @@ -1913,7 +1913,7 @@ open class LibraryController( isGone = true setOnClickListener { presenter.forceShowAllCategories = !presenter.forceShowAllCategories - presenter.getLibrary() + presenter.updateLibrary() isSelected = presenter.forceShowAllCategories } val pad = 12.dpToPx @@ -2193,7 +2193,7 @@ open class LibraryController( val activity = activity ?: return viewScope.launchIO { selectedMangas.toList().moveCategories(activity) { - presenter.getLibrary() + presenter.updateLibrary() destroyActionModeIfNeeded() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 77ed4ca035..1c141d99a2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.removeCover import eu.kanade.tachiyomi.data.database.models.seriesType +import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.preference.DelayedLibrarySuggestionsJob import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -51,11 +52,9 @@ import kotlin.random.Random import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.retry import kotlinx.coroutines.flow.shareIn @@ -89,6 +88,7 @@ class LibraryPresenter( private val preferences: PreferencesHelper = Injekt.get(), private val coverCache: CoverCache = Injekt.get(), val sourceManager: SourceManager = Injekt.get(), + private val downloadCache: DownloadCache = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(), private val chapterFilter: ChapterFilter = Injekt.get(), private val trackManager: TrackManager = Injekt.get(), @@ -103,10 +103,7 @@ class LibraryPresenter( private val getTrack: GetTrack by injectLazy() private val getHistory: GetHistory by injectLazy() - private val _fetchLibrary: Channel = Channel(Channel.UNLIMITED) - val fetchLibrary = _fetchLibrary.receiveAsFlow() - .onStart { emit(Unit) } - .shareIn(presenterScope, SharingStarted.Lazily, 1) + private val forceUpdateEvent: Channel = Channel(Channel.UNLIMITED) private val context = preferences.context private val viewContext @@ -129,6 +126,8 @@ class LibraryPresenter( private var allCategories: List = emptyList() /** List of all manga to update the */ + // TODO: Store sectioned before flattening it out for "show all categories" + private var currentLibrary: Map> = mapOf() var libraryItems: List = emptyList() private var sectionedLibraryItems: MutableMap> = mutableMapOf() var currentCategory = -1 @@ -197,6 +196,7 @@ class LibraryPresenter( } subscribeLibrary() + updateLibrary() if (!preferences.showLibrarySearchSuggestions().isSet()) { DelayedLibrarySuggestionsJob.setupTask(context, true) @@ -233,38 +233,29 @@ class LibraryPresenter( combine( getLibraryFlow(), - fetchLibrary, - ) { data, _ -> + downloadCache.changes, + ) { data, _ -> data }.collectLatest { data -> categories = data.categories allCategories = data.allCategories - data.items + val library = data.items + val hiddenItems = library.filter { it.manga.isHidden() }.mapNotNull { it.manga.items }.flatten() + + setDownloadCount(library) + setUnreadBadge(library) + setSourceLanguage(library) + setDownloadCount(hiddenItems) + setUnreadBadge(hiddenItems) + setSourceLanguage(hiddenItems) + + allLibraryItems = library + hiddenLibraryItems = hiddenItems + val mangaMap = library + .applyFilters() + .applySort() + val freshStart = libraryItems.isEmpty() + sectionLibrary(mangaMap, freshStart) } - .collectLatest { library -> - val hiddenItems = library.filter { it.manga.isHidden() }.mapNotNull { it.manga.items }.flatten() - - setDownloadCount(library) - setUnreadBadge(library) - setSourceLanguage(library) - setDownloadCount(hiddenItems) - setUnreadBadge(hiddenItems) - setSourceLanguage(hiddenItems) - - allLibraryItems = library - hiddenLibraryItems = hiddenItems - val mangaMap = library - .applyFilters() - .applySort() - val freshStart = libraryItems.isEmpty() - sectionLibrary(mangaMap, freshStart) - } - } - } - - /** Get favorited manga for library and sort and filter it */ - fun getLibrary() { - presenterScope.launch { - _fetchLibrary.send(Unit) } } @@ -321,8 +312,7 @@ class LibraryPresenter( private suspend fun sectionLibrary(items: List, freshStart: Boolean = false) { libraryItems = items - val showAll = showAllCategories || !libraryIsGrouped || - categories.size <= 1 + val showAll = showAllCategories || !libraryIsGrouped || categories.size <= 1 sectionedLibraryItems = items.groupBy { it.header.category.id ?: 0 }.toMutableMap() if (!showAll && currentCategory == -1) { currentCategory = categories.find { @@ -758,6 +748,9 @@ class LibraryPresenter( preferences.librarySortingMode().changes(), preferences.librarySortingAscending().changes(), + + preferences.collapsedCategories().changes(), + preferences.collapsedDynamicCategories().changes(), ) { ItemPreferences( filterDownloaded = it[0] as Int, @@ -771,6 +764,8 @@ class LibraryPresenter( showAllCategories = it[8] as Boolean, sortingMode = it[9] as Int, sortAscending = it[10] as Boolean, + collapsedCategories = it[11] as Set, + collapsedDynamicCategories = it[12] as Set, ) } @@ -808,26 +803,27 @@ class LibraryPresenter( * If category id '-1' is not empty, it means the library not grouped by categories */ private fun getLibraryFlow(): Flow { - return combine( + val libraryFlow = combine( getCategories.subscribe(), // FIXME: Remove retry once a real solution is found getLibraryManga.subscribe().retry(1) { e -> e is NullPointerException }, getPreferencesFlow(), - preferences.removeArticles().changes(), - fetchLibrary - ) { dbCategories, libraryMangaList, prefs, removeArticles, _ -> + forceUpdateEvent.receiveAsFlow(), + ) { dbCategories, libraryMangaList, prefs, _ -> groupType = prefs.groupType val defaultCategory = createDefaultCategory() val allCategories = listOf(defaultCategory) + dbCategories - val (items, categories, hiddenItems) = if (groupType <= BY_DEFAULT || !libraryIsGrouped) { + // FIXME: Should return Map where Int is category id + if (groupType <= BY_DEFAULT || !libraryIsGrouped) { getLibraryItems( - dbCategories, + allCategories, // FIXME: Don't depends on allCategories libraryMangaList, prefs.sortingMode, prefs.sortAscending, prefs.showAllCategories, + prefs.collapsedCategories, defaultCategory, ) } else { @@ -836,8 +832,17 @@ class LibraryPresenter( prefs.sortingMode, prefs.sortAscending, groupType, + prefs.collapsedDynamicCategories, ) - } + } to allCategories + } + + return combine( + libraryFlow, + preferences.removeArticles().changes(), + ) { library, removeArticles -> + val (libraryItems, allCategories) = library + val (items, categories, hiddenItems) = libraryItems LibraryData( categories = categories, @@ -855,6 +860,7 @@ class LibraryPresenter( sortingMode: Int, isAscending: Boolean, showAll: Boolean, + collapsedCategories: Set, defaultCategory: Category, ): Triple, List, List> { val categories = allCategories.toMutableList() @@ -878,6 +884,11 @@ class LibraryPresenter( } + (-1 to catItemAll) + (0 to LibraryHeaderItem({ categories.getOrDefault(0) }, 0)) ).toMap() + // TODO + val map = libraryManga.groupBy { + categories.getOrDefault(it.category) + } + val items = if (libraryIsGrouped) { libraryManga } else { @@ -897,16 +908,14 @@ class LibraryPresenter( val categoriesHidden = if (forceShowAllCategories || controllerIsSubClass) { emptySet() } else { - preferences.collapsedCategories().get().mapNotNull { it.toIntOrNull() }.toSet() + collapsedCategories.mapNotNull { it.toIntOrNull() }.toSet() } if (categorySet.contains(0)) categories.add(0, defaultCategory) if (libraryIsGrouped) { categories.forEach { category -> val catId = category.id ?: return@forEach - if (catId > 0 && !categorySet.contains(catId) && - (catId !in categoriesHidden || !showAll) - ) { + if (catId > 0 && !categorySet.contains(catId) && (catId !in categoriesHidden || !showAll)) { val headerItem = headerItems[catId] if (headerItem != null) { items.add( @@ -959,6 +968,7 @@ class LibraryPresenter( sortingMode: Int, isAscending: Boolean, groupType: Int, + collapsedDynamicCategories: Set, ): Triple, List, List> { val tagItems: MutableMap = mutableMapOf() @@ -1061,7 +1071,7 @@ class LibraryPresenter( val hiddenDynamics = if (controllerIsSubClass) { emptySet() } else { - preferences.collapsedDynamicCategories().get() + collapsedDynamicCategories } val headers = tagItems.map { item -> Category.createCustom( @@ -1217,7 +1227,6 @@ class LibraryPresenter( .mapNotNull { if (it.id != null) MangaUpdate(it.id!!, favorite = false) else null } withIOContext { updateManga.awaitAll(mangaToDelete) } - getLibrary() } } @@ -1240,8 +1249,11 @@ class LibraryPresenter( } } - /** Called when Library Service updates a manga, update the item as well */ - fun updateManga() = getLibrary() + /** Force update the library */ + fun updateLibrary() = presenterScope.launch { + forceUpdateEvent.send(Unit) + } + /** Undo the removal of the manga once in library */ fun reAddMangas(mangas: List) { @@ -1251,12 +1263,12 @@ class LibraryPresenter( withIOContext { updateManga.awaitAll(mangaToAdd) } (view as? FilteredLibraryController)?.updateStatsPage() - getLibrary() } } /** Returns first unread chapter of a manga */ fun getFirstUnread(manga: Manga): Chapter? { + // FIXME: Don't do blocking val chapters = runBlocking { getChapter.awaitAll(manga) } return ChapterSort(manga, chapterFilter, preferences).getNextUnreadChapter(chapters, false) } @@ -1308,7 +1320,6 @@ class LibraryPresenter( } } - // TODO: Use SQLDelight /** Shift a manga's category via drag & drop */ fun moveMangaToCategory( manga: LibraryManga, @@ -1354,7 +1365,7 @@ class LibraryPresenter( ) } } - getLibrary() + updateLibrary() } } @@ -1389,7 +1400,6 @@ class LibraryPresenter( } preferences.collapsedDynamicCategories().set(categoriesHidden) } - getLibrary() } private fun getDynamicCategoryName(category: Category): String = @@ -1420,7 +1430,6 @@ class LibraryPresenter( } } } - getLibrary() } fun allCategoriesExpanded(): Boolean { @@ -1462,7 +1471,7 @@ class LibraryPresenter( mapMangaChapters[manga] = chapters } - getLibrary() + updateLibrary() } return mapMangaChapters } @@ -1478,7 +1487,7 @@ class LibraryPresenter( } }.flatten() updateChapter.awaitAll(updates) - getLibrary() + updateLibrary() } } @@ -1651,6 +1660,9 @@ class LibraryPresenter( val sortingMode: Int, val sortAscending: Boolean, + + val collapsedCategories: Set, + val collapsedDynamicCategories: Set, ) data class LibraryData( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt index b409856977..0dc927b9e0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/display/LibraryCategoryView.kt @@ -2,16 +2,14 @@ package eu.kanade.tachiyomi.ui.library.display import android.content.Context import android.util.AttributeSet -import eu.kanade.tachiyomi.R -import yokai.i18n.MR -import yokai.util.lang.getString -import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.databinding.LibraryCategoryLayoutBinding import eu.kanade.tachiyomi.util.bindToPreference import eu.kanade.tachiyomi.util.lang.withSubtitle import eu.kanade.tachiyomi.util.system.toInt import eu.kanade.tachiyomi.widget.BaseLibraryDisplayView import kotlin.math.min +import yokai.i18n.MR +import yokai.util.lang.getString class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : BaseLibraryDisplayView(context, attrs) { @@ -20,7 +18,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att override fun initGeneralPreferences() { with(binding) { showAll.bindToPreference(preferences.showAllCategories()) { - controller?.presenter?.getLibrary() + controller?.presenter?.updateLibrary() binding.categoryShow.isEnabled = it } categoryShow.isEnabled = showAll.isChecked @@ -30,7 +28,7 @@ class LibraryCategoryView @JvmOverloads constructor(context: Context, attrs: Att dynamicToBottom.text = context.getString(MR.strings.move_dynamic_to_bottom) .withSubtitle(context, MR.strings.when_grouping_by_sources_tags) dynamicToBottom.bindToPreference(preferences.collapsedDynamicAtBottom()) { - controller?.presenter?.getLibrary() + controller?.presenter?.updateLibrary() } showEmptyCatsFiltering.bindToPreference(preferences.showEmptyCategoriesWhileFiltering()) { controller?.presenter?.requestFilterUpdate() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt index dbf17c3c49..9950858541 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt @@ -36,6 +36,8 @@ import eu.kanade.tachiyomi.util.view.isCollapsed import eu.kanade.tachiyomi.util.view.isExpanded import eu.kanade.tachiyomi.util.view.isHidden import eu.kanade.tachiyomi.util.view.setText +import kotlin.math.max +import kotlin.math.roundToInt import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.drop @@ -48,8 +50,6 @@ import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import yokai.i18n.MR import yokai.util.lang.getString -import kotlin.math.max -import kotlin.math.roundToInt class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs), @@ -637,7 +637,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri SeriesType('m', MR.strings.series_type), Bookmarked('b', MR.strings.bookmarked), Tracked('t', MR.strings.tracking), - ContentType('s', MR.strings.content_type); + ContentType('s', MR.strings.content_type) ; companion object { From 90f5dfc55af31108fc981abcff3c781f893da9cc Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 23 Dec 2024 07:28:43 +0700 Subject: [PATCH 002/181] fix(library): Don't show Default category if it's empty --- .../kanade/tachiyomi/ui/library/LibraryPresenter.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 1c141d99a2..b2d43befea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -813,12 +813,11 @@ class LibraryPresenter( groupType = prefs.groupType val defaultCategory = createDefaultCategory() - val allCategories = listOf(defaultCategory) + dbCategories // FIXME: Should return Map where Int is category id if (groupType <= BY_DEFAULT || !libraryIsGrouped) { getLibraryItems( - allCategories, // FIXME: Don't depends on allCategories + dbCategories, libraryMangaList, prefs.sortingMode, prefs.sortAscending, @@ -834,7 +833,7 @@ class LibraryPresenter( groupType, prefs.collapsedDynamicCategories, ) - } to allCategories + } to listOf(defaultCategory) + dbCategories } return combine( @@ -884,10 +883,10 @@ class LibraryPresenter( } + (-1 to catItemAll) + (0 to LibraryHeaderItem({ categories.getOrDefault(0) }, 0)) ).toMap() - // TODO - val map = libraryManga.groupBy { - categories.getOrDefault(it.category) - } + // TODO: - + // val map = libraryManga.groupBy { + // categories.getOrDefault(it.category) + // } val items = if (libraryIsGrouped) { libraryManga From bcf21858ee199e20508e74870adb84a309bd1dae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 08:29:38 +0700 Subject: [PATCH 003/181] fix(deps): Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.1 (#321) * fix(deps): Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.1 * docs: Sync changelog --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ahmad Ansori Palembani --- CHANGELOG.md | 5 +++++ gradle/kotlinx.versions.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e7a58072..1efe310b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - `Translation` - Translation changes/updates - `Other` - Technical changes/updates +## [Unreleased] + +### Other +- Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.1 + ## [1.9.7] ### Changes diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index 2384d6c058..9fcc9e314c 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -4,7 +4,7 @@ serialization = "1.7.3" xml_serialization = "0.90.3" [libraries] -coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version = "1.9.0" } +coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version = "1.10.1" } coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" } coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" } coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test" } From 3fa475c1cf626a2b5859a1f11823e0fb0d4bd0fc Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 23 Dec 2024 08:50:32 +0700 Subject: [PATCH 004/181] refactor: Move isShizukuInstalled check --- .../java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt | 5 ++--- .../eu/kanade/tachiyomi/util/system/ContextExtensions.kt | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt index de12d213bb..a19fd185c4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt @@ -15,7 +15,7 @@ import yokai.util.lang.getString import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID import eu.kanade.tachiyomi.util.system.getUriSize -import eu.kanade.tachiyomi.util.system.isPackageInstalled +import eu.kanade.tachiyomi.util.system.isShizukuInstalled import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -23,7 +23,6 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import rikka.shizuku.Shizuku import rikka.shizuku.ShizukuRemoteProcess -import rikka.sui.Sui import uy.kohesive.injekt.injectLazy import java.io.BufferedReader import java.io.InputStream @@ -75,7 +74,7 @@ class ShizukuInstaller(private val context: Context, val finishedQueue: (Shizuku init { Shizuku.addBinderDeadListener(shizukuDeadListener) - require(Shizuku.pingBinder() && (context.isPackageInstalled(shizukuPkgName) || Sui.isSui())) { + require(Shizuku.pingBinder() && context.isShizukuInstalled) { finishedQueue(this) context.getString(MR.strings.ext_installer_shizuku_stopped) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index 0cd311e68c..e0dfd4e2f3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -47,6 +47,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.delay import kotlinx.coroutines.withContext +import rikka.sui.Sui import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import yokai.i18n.MR @@ -207,11 +208,13 @@ fun Context.isPackageInstalled(packageName: String): Boolean { return try { packageManager.getApplicationInfoCompat(packageName, 0) true - } catch (_: Exception) { + } catch (_: PackageManager.NameNotFoundException) { false } } +val Context.isShizukuInstalled get() = isPackageInstalled("moe.shizuku.privileged.api") || Sui.isSui() + /** * Property to get the notification manager from the context. */ From 37f47ae2f55ebc621feaf088bf615bef826e1831 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 23 Dec 2024 08:53:27 +0700 Subject: [PATCH 005/181] fix(onboarding): Allow user to skip onboarding if Shizuku is installed Fixes GH-322 --- .../java/yokai/presentation/onboarding/steps/PermissionStep.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/yokai/presentation/onboarding/steps/PermissionStep.kt b/app/src/main/java/yokai/presentation/onboarding/steps/PermissionStep.kt index 056660af2e..2f4aa9c3a5 100644 --- a/app/src/main/java/yokai/presentation/onboarding/steps/PermissionStep.kt +++ b/app/src/main/java/yokai/presentation/onboarding/steps/PermissionStep.kt @@ -34,6 +34,7 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.system.isShizukuInstalled import yokai.i18n.MR import yokai.util.lang.getString import dev.icerock.moko.resources.compose.stringResource @@ -66,7 +67,7 @@ internal class PermissionStep : OnboardingStep { context.contentResolver, Settings.Secure.INSTALL_NON_MARKET_APPS ) != 0 - } + } || context.isShizukuInstalled notificationGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED From 2b953a53d80c26018ac426da811303a7984a6719 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 23 Dec 2024 09:12:53 +0700 Subject: [PATCH 006/181] docs: Sync changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1efe310b88..ca40a83ce0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,11 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co ## [Unreleased] +### Fixes +- Allow users to bypass onboarding's permission step if Shizuku is installed + ### Other +- Refactor Library to utilize Flow even more - Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.1 ## [1.9.7] From b1665eaedfe266b959ed22cfdd4be429f84744c0 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 23 Dec 2024 09:26:45 +0700 Subject: [PATCH 007/181] chore(i18n): Rephrasing --- i18n/src/commonMain/moko-resources/base/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 92b218f85f..8f00790c70 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -500,7 +500,7 @@ Start past cutout Cutout area behavior only applies in portrait mode with certain scale types Open legacy cutout settings - On devices older than Android 9.0, there\'s no way to modify cutout setting other than setting it manually through to your system settings + On devices older than Android 9.0, you need to set cutout settings manually through your system settings Show content in cutout area Ignore cutout areas Page layout @@ -794,8 +794,8 @@ Clear chapter cache - Refresh the download cache - This will force the download cache to recalculate. Useful if you modified downloads outside of this app and want the app to pick them up + Reindex downloads + Force app to recheck downloaded chapters Data Management Check for beta releases From 5824ac81c2b5085317114cd882473d8891c75048 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 23 Dec 2024 20:58:30 +0700 Subject: [PATCH 008/181] refactor: Use Compose for EmptyView --- .../ui/download/DownloadBottomSheet.kt | 4 +- .../tachiyomi/ui/library/LibraryController.kt | 4 +- .../ui/manga/track/TrackingBottomSheet.kt | 7 +- .../stats/details/StatsDetailsController.kt | 7 +- .../tachiyomi/ui/recents/RecentsController.kt | 7 +- .../tachiyomi/ui/recents/RecentsPresenter.kt | 4 +- .../database/ClearDatabaseController.kt | 4 +- .../source/browse/BrowseSourceController.kt | 11 +- .../eu/kanade/tachiyomi/util/WindowSize.kt | 12 ++ .../util/system/ContextExtensions.kt | 5 +- .../eu/kanade/tachiyomi/widget/EmptyView.kt | 113 ++++++++------ .../presentation/component/EmptyScreen.kt | 143 ++++++++++++++---- .../extension/repo/ExtensionRepoScreen.kt | 2 + 13 files changed, 228 insertions(+), 95 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/WindowSize.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadBottomSheet.kt index ef9a074b29..0afa1b2af0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadBottomSheet.kt @@ -5,6 +5,8 @@ import android.content.Context import android.util.AttributeSet import android.view.MenuItem import android.widget.LinearLayout +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.FileDownloadOff import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.isInvisible import androidx.core.view.updateLayoutParams @@ -212,7 +214,7 @@ class DownloadBottomSheet @JvmOverloads constructor( setBottomSheet() if (presenter.downloadQueueState.value.isEmpty()) { binding.emptyView.show( - R.drawable.ic_download_off_24dp, + Icons.Filled.FileDownloadOff, MR.strings.nothing_is_downloading, ) } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index bf10138a82..393fe3244d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -28,6 +28,8 @@ import android.widget.ImageView import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.HeartBroken import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.animation.doOnEnd import androidx.core.view.ViewCompat @@ -1135,7 +1137,7 @@ open class LibraryController( binding.emptyView.hide() } else { binding.emptyView.show( - R.drawable.ic_heart_off_24dp, + Icons.Filled.HeartBroken, if (hasActiveFilters) { MR.strings.no_matches_for_filters } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt index 5fcdb7a29b..b8b6e8479e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackingBottomSheet.kt @@ -18,6 +18,8 @@ import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.PopupMenu +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.SearchOff import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.net.toUri import androidx.core.view.WindowInsetsCompat.Type.systemBars @@ -31,7 +33,6 @@ import com.google.android.material.datepicker.MaterialDatePicker import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.adapters.ItemAdapter import com.mikepenz.fastadapter.listeners.addClickListener -import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.model.TrackSearch @@ -317,7 +318,7 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : if (results.isEmpty()) { setMiddleTrackView(binding.searchEmptyView.id) binding.searchEmptyView.show( - R.drawable.ic_search_off_24dp, + Icons.Filled.SearchOff, MR.strings.no_results_found, ) } else { @@ -338,7 +339,7 @@ class TrackingBottomSheet(private val controller: MangaDetailsController) : binding.trackSearchRecycler.isVisible = false searchItemAdapter.clear() binding.searchEmptyView.show( - R.drawable.ic_search_off_24dp, + Icons.Filled.SearchOff, error.message ?: "", ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsController.kt index 7636389cee..c9d366fb39 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsController.kt @@ -12,6 +12,8 @@ import android.view.View import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.HeartBroken import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.graphics.ColorUtils import androidx.core.util.Pair @@ -458,7 +460,10 @@ class StatsDetailsController : with(binding ?: headerBinding) { val hasNoData = currentStats.isNullOrEmpty() || currentStats.all { it.count == 0 } if (hasNoData) { - this@StatsDetailsController.binding.noChartData.show(R.drawable.ic_heart_off_24dp, MR.strings.no_data_for_filters) + this@StatsDetailsController.binding.noChartData.show( + Icons.Filled.HeartBroken, + MR.strings.no_data_for_filters, + ) presenter.currentStats?.removeAll { it.count == 0 } handleNoChartLayout() this?.statsPieChart?.isVisible = false diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt index 022d491fd4..b4ec025691 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsController.kt @@ -12,6 +12,9 @@ import android.view.RoundedCorner import android.view.View import android.view.ViewGroup import androidx.activity.BackEventCompat +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.HistoryToggleOff +import androidx.compose.material.icons.filled.SearchOff import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -596,9 +599,9 @@ class RecentsController(bundle: Bundle? = null) : if (recents.isEmpty()) { binding.recentsEmptyView.show( if (!isSearching()) { - R.drawable.ic_history_off_24dp + Icons.Filled.HistoryToggleOff } else { - R.drawable.ic_search_off_24dp + Icons.Filled.SearchOff }, if (isSearching()) { MR.strings.no_results_found diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt index 138df111a7..24a5f4ca7c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt @@ -75,8 +75,8 @@ class RecentsPresenter( } private val newAdditionsHeader = RecentMangaHeaderItem(RecentMangaHeaderItem.NEWLY_ADDED) private val newChaptersHeader = RecentMangaHeaderItem(RecentMangaHeaderItem.NEW_CHAPTERS) - private val continueReadingHeader = - RecentMangaHeaderItem(RecentMangaHeaderItem.CONTINUE_READING) + private val continueReadingHeader = RecentMangaHeaderItem(RecentMangaHeaderItem.CONTINUE_READING) + var finished = false private var shouldMoveToTop = false var viewType: RecentsViewType = RecentsViewType.valueOf(uiPreferences.recentsViewType().get()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/database/ClearDatabaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/database/ClearDatabaseController.kt index 5c74237139..316589f6cd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/database/ClearDatabaseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/database/ClearDatabaseController.kt @@ -7,6 +7,8 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Book import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.forEach import androidx.core.view.isInvisible @@ -193,7 +195,7 @@ class ClearDatabaseController : binding.emptyView.hide() } else { binding.emptyView.show( - R.drawable.ic_book_24dp, + Icons.Filled.Book, MR.strings.database_clean, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt index 8eb367d84b..cbf0973b0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt @@ -8,6 +8,8 @@ import android.view.MenuInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ExploreOff import androidx.core.view.WindowInsetsCompat.Type.ime import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.isVisible @@ -62,7 +64,6 @@ import eu.kanade.tachiyomi.widget.EmptyView import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset import kotlin.math.roundToInt import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import uy.kohesive.injekt.injectLazy @@ -577,7 +578,7 @@ open class BrowseSourceController(bundle: Bundle) : snack?.dismiss() val message = getErrorMessage(error) - val retryAction = View.OnClickListener { + val retryAction = { // If not the first page, show bottom binding.progress bar. if (adapter.mainItemCount > 0 && progressItem != null) { adapter.addScrollableFooterWithDelay(progressItem!!, 0, true) @@ -606,16 +607,16 @@ open class BrowseSourceController(bundle: Bundle) : binding.emptyView.show( if (presenter.source is HttpSource) { - R.drawable.ic_browse_off_24dp + EmptyView.Image.Vector(Icons.Filled.ExploreOff) } else { - R.drawable.ic_local_library_24dp + EmptyView.Image.ResourceVector(R.drawable.ic_local_library_24dp) }, message, actions, ) } else { snack = binding.sourceLayout.snack(message, Snackbar.LENGTH_INDEFINITE) { - setAction(MR.strings.retry, retryAction) + setAction(MR.strings.retry) { retryAction() } } } if (isControllerVisible) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/WindowSize.kt b/app/src/main/java/eu/kanade/tachiyomi/util/WindowSize.kt new file mode 100644 index 0000000000..311c1f34fd --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/WindowSize.kt @@ -0,0 +1,12 @@ +package eu.kanade.tachiyomi.util + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.platform.LocalConfiguration +import eu.kanade.tachiyomi.util.system.isTablet + +@Composable +@ReadOnlyComposable +fun isTablet(): Boolean { + return LocalConfiguration.current.isTablet() +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index e0dfd4e2f3..91943b465d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -54,6 +54,8 @@ import yokai.i18n.MR private const val TABLET_UI_MIN_SCREEN_WIDTH_DP = 720 +private const val TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP = 600 + /** * Helper method to create a notification. * @@ -113,7 +115,8 @@ fun Float.dpToPxEnd(resources: Resources): Float { val Resources.isLTR get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR -fun Context.isTablet() = resources.configuration.smallestScreenWidthDp >= 600 +fun Configuration.isTablet() = smallestScreenWidthDp >= TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP +fun Context.isTablet() = resources.configuration.isTablet() val displayMaxHeightInPx: Int get() = Resources.getSystem().displayMetrics.let { max(it.heightPixels, it.widthPixels) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt index 6bbaa86939..7a419059d3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt @@ -2,28 +2,60 @@ package eu.kanade.tachiyomi.widget import android.content.Context import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.RelativeLayout -import androidx.annotation.DrawableRes +import android.view.Gravity +import android.widget.FrameLayout import androidx.annotation.StringRes +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Download +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.AbstractComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.vectorResource import androidx.core.view.isVisible -import androidx.core.view.updateLayoutParams -import com.google.android.material.button.MaterialButton import dev.icerock.moko.resources.StringResource -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.databinding.CommonViewEmptyBinding -import eu.kanade.tachiyomi.util.view.setText -import eu.kanade.tachiyomi.util.view.setVectorCompat +import eu.kanade.tachiyomi.util.isTablet +import yokai.presentation.component.EmptyScreen +import yokai.presentation.theme.YokaiTheme import yokai.util.lang.getString -import android.R as AR -class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - RelativeLayout(context, attrs) { +class EmptyView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : AbstractComposeView(context, attrs, defStyleAttr) { - private val binding: CommonViewEmptyBinding = - CommonViewEmptyBinding.inflate(LayoutInflater.from(context), this, true) + private var image by mutableStateOf(Image.Vector(Icons.Filled.Download)) + private var message by mutableStateOf("") + private var actions by mutableStateOf(emptyList()) + + init { + layoutParams = FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER) + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool) + } + + @Composable + fun image(): ImageVector { + return when (image) { + is Image.Vector -> (image as Image.Vector).image + is Image.ResourceVector -> ImageVector.vectorResource((image as Image.ResourceVector).id) + } + } + + @Composable + override fun Content() { + YokaiTheme { + EmptyScreen( + image = image(), + message = message, + isTablet = isTablet(), + actions = actions, + ) + } + } /** * Hide the information view @@ -36,16 +68,21 @@ class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? * Show the information view * @param textResource text of information view */ - fun show(@DrawableRes drawable: Int, textResource: StringResource, actions: List? = null) { - show(drawable, context.getString(textResource), actions) + fun show(image: ImageVector, textResource: StringResource, actions: List = emptyList()) { + show(Image.Vector(image), context.getString(textResource), actions) } /** * Show the information view * @param textResource text of information view */ - fun show(@DrawableRes drawable: Int, @StringRes textResource: Int, actions: List? = null) { - show(drawable, context.getString(textResource), actions) + fun show(image: ImageVector, @StringRes textResource: Int, actions: List = emptyList()) { + show(Image.Vector(image), context.getString(textResource), actions) + } + + @Deprecated("Use EmptyView.Image instead of passing ImageVector directly") + fun show(image: ImageVector, message: String, actions: List = emptyList()) { + show(Image.Vector(image), message, actions) } /** @@ -53,36 +90,20 @@ class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? * @param drawable icon of information view * @param textResource text of information view */ - fun show(@DrawableRes drawable: Int, message: String, actions: List? = null) { - binding.imageView.setVectorCompat(drawable, AR.attr.textColorHint) - binding.textLabel.text = message - - binding.actionsContainer.removeAllViews() - binding.actionsContainer.isVisible = !actions.isNullOrEmpty() - if (!actions.isNullOrEmpty()) { - actions.forEach { - val button = - (inflate(context, R.layout.material_text_button, null) as MaterialButton) - .apply { - setText(it.resId) - setOnClickListener(it.listener) - } - binding.actionsContainer.addView(button) - if (context.resources.configuration.screenHeightDp < 600) { - button.textAlignment = View.TEXT_ALIGNMENT_TEXT_START - button.updateLayoutParams { - width = ViewGroup.LayoutParams.WRAP_CONTENT - height = ViewGroup.LayoutParams.WRAP_CONTENT - } - } - } - } - + fun show(image: Image, message: String, actions: List = emptyList()) { + this.image = image + this.message = message + this.actions = actions this.isVisible = true } data class Action( val resId: StringResource, - val listener: OnClickListener, + val listener: () -> Unit, ) + + sealed class Image { + data class Vector(val image: ImageVector) : Image() + data class ResourceVector(val id: Int) : Image() + } } diff --git a/app/src/main/java/yokai/presentation/component/EmptyScreen.kt b/app/src/main/java/yokai/presentation/component/EmptyScreen.kt index dc9d4c66df..2d5925a6a9 100644 --- a/app/src/main/java/yokai/presentation/component/EmptyScreen.kt +++ b/app/src/main/java/yokai/presentation/component/EmptyScreen.kt @@ -4,6 +4,8 @@ import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -13,6 +15,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Download import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -21,7 +24,11 @@ import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.util.compose.textHint +import eu.kanade.tachiyomi.widget.EmptyView +import yokai.i18n.MR private val defaultIconModifier = Modifier.size(128.dp) @@ -34,8 +41,9 @@ fun EmptyScreen( modifier: Modifier = Modifier, image: ImageVector, message: String, - actions: @Composable () -> Unit = {}, -) = EmptyScreen( + isTablet: Boolean, + actions: List = emptyList(), +) = EmptyScreenImpl( modifier = modifier, image = { Image( @@ -46,7 +54,8 @@ fun EmptyScreen( ) }, message = message, - actions = actions, + actions = { EmptyScreenActions(actions, isTablet) }, + isTablet = isTablet, ) @Composable @@ -54,8 +63,9 @@ fun EmptyScreen( modifier: Modifier = Modifier, image: ImageBitmap, message: String, - actions: @Composable () -> Unit = {}, -) = EmptyScreen( + isTablet: Boolean, + actions: List = emptyList(), +) = EmptyScreenImpl( modifier = modifier, image = { Image( @@ -65,35 +75,104 @@ fun EmptyScreen( ) }, message = message, - actions = actions, + actions = { EmptyScreenActions(actions, isTablet) }, + isTablet = isTablet, ) -@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO, showBackground = true) @Composable -private fun EmptyScreen( - modifier: Modifier = Modifier, - image: @Composable () -> Unit = { - Image(modifier = defaultIconModifier, imageVector = Icons.Filled.Download, contentDescription = null) - }, - message: String = "Something went wrong", - actions: @Composable () -> Unit = {}, -) { - Column( - modifier = modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - image() - Text( - modifier = Modifier - .padding(top = 16.dp), - text = message, - color = MaterialTheme.colorScheme.textHint, - style = MaterialTheme.typography.labelMedium, - ) - actions() +private fun EmptyScreenActions(actions: List, isTablet: Boolean) { + if (isTablet) { + FlowRow { + actions.forEach { action -> + TextButton(onClick = { action.listener() }) { + Text( + text = stringResource(action.resId), + fontSize = 14.sp, + ) + } + } + } + } else { + Column( + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + actions.forEach { action -> + TextButton(onClick = { action.listener() }) { + Text( + text = stringResource(action.resId), + fontSize = 14.sp, + ) + } + } + } } } + +@Composable +private fun EmptyScreenImpl( + modifier: Modifier = Modifier, + image: @Composable () -> Unit, + message: String, + actions: @Composable () -> Unit, + isTablet: Boolean, +) { + if (isTablet) { + Column( + modifier = modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Row { + image() + Text( + modifier = Modifier + .padding(vertical = 4.dp), + text = message, + color = MaterialTheme.colorScheme.textHint, + style = MaterialTheme.typography.labelMedium, + ) + } + actions() + } + } else { + Column( + modifier = modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + image() + Text( + modifier = Modifier + .padding(vertical = 16.dp), + text = message, + color = MaterialTheme.colorScheme.textHint, + style = MaterialTheme.typography.labelMedium, + ) + actions() + } + } +} + +@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) +@Preview(name = "Light", uiMode = Configuration.UI_MODE_NIGHT_NO, showBackground = true) +@Composable +private fun EmptyScreenPreview() { + EmptyScreen( + image = Icons.Filled.Download, + message = "Something went wrong", + actions = listOf( + EmptyView.Action(MR.strings.download) {}, + EmptyView.Action(MR.strings.download) {}, + EmptyView.Action(MR.strings.download) {}, + EmptyView.Action(MR.strings.download) {}, + EmptyView.Action(MR.strings.download) {}, + ), + isTablet = false, + ) +} diff --git a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt index 47a756a7bb..ed93b2b846 100644 --- a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt +++ b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt @@ -29,6 +29,7 @@ import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.util.compose.LocalAlertDialog import eu.kanade.tachiyomi.util.compose.LocalBackPress import eu.kanade.tachiyomi.util.compose.currentOrThrow +import eu.kanade.tachiyomi.util.isTablet import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.flow.collectLatest import yokai.domain.ComposableAlertDialog @@ -101,6 +102,7 @@ fun ExtensionRepoScreen( modifier = Modifier.fillParentMaxSize(), image = Icons.Filled.ExtensionOff, message = stringResource(MR.strings.information_empty_repos), + isTablet = isTablet(), ) } return@LazyColumn From 76af51a319335e17e1cd8d35708da0f60a1affcc Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 23 Dec 2024 21:06:20 +0700 Subject: [PATCH 009/181] docs: Sync changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca40a83ce0..921994ef66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co ### Other - Refactor Library to utilize Flow even more - Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.1 +- Refactor EmptyView to use Compose ## [1.9.7] From afb7e79ea4a794a31de7b0df2c6d44422f6c7af0 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 23 Dec 2024 21:17:45 +0700 Subject: [PATCH 010/181] fix(EmptyScreen): Align buttons center --- app/src/main/java/yokai/presentation/component/EmptyScreen.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/yokai/presentation/component/EmptyScreen.kt b/app/src/main/java/yokai/presentation/component/EmptyScreen.kt index 2d5925a6a9..af5c011257 100644 --- a/app/src/main/java/yokai/presentation/component/EmptyScreen.kt +++ b/app/src/main/java/yokai/presentation/component/EmptyScreen.kt @@ -95,6 +95,7 @@ private fun EmptyScreenActions(actions: List, isTablet: Boolea } else { Column( verticalArrangement = Arrangement.spacedBy(4.dp), + horizontalAlignment = Alignment.CenterHorizontally, ) { actions.forEach { action -> TextButton(onClick = { action.listener() }) { From 99bec410560178d1cd3e8c6bdc134c3e77323322 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 23 Dec 2024 21:29:56 +0700 Subject: [PATCH 011/181] style(EmptyScreen): Adjust spacing --- app/src/main/java/yokai/presentation/component/EmptyScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/yokai/presentation/component/EmptyScreen.kt b/app/src/main/java/yokai/presentation/component/EmptyScreen.kt index af5c011257..b68d9dc66d 100644 --- a/app/src/main/java/yokai/presentation/component/EmptyScreen.kt +++ b/app/src/main/java/yokai/presentation/component/EmptyScreen.kt @@ -94,7 +94,7 @@ private fun EmptyScreenActions(actions: List, isTablet: Boolea } } else { Column( - verticalArrangement = Arrangement.spacedBy(4.dp), + verticalArrangement = Arrangement.spacedBy(2.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { actions.forEach { action -> From f3cac7cac8c2e51467d3f3b4347fa766c30f9367 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 24 Dec 2024 07:05:01 +0700 Subject: [PATCH 012/181] refactor: Transform LocalSource icon from XML vector to ImageVector --- .../source/browse/BrowseSourceController.kt | 5 ++- .../eu/kanade/tachiyomi/widget/EmptyView.kt | 29 +++----------- .../component/icons/LocalSource.kt | 39 +++++++++++++++++++ 3 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/yokai/presentation/component/icons/LocalSource.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt index cbf0973b0c..89989372e0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt @@ -69,6 +69,7 @@ import kotlinx.coroutines.launch import uy.kohesive.injekt.injectLazy import yokai.domain.manga.interactor.GetManga import yokai.i18n.MR +import yokai.presentation.component.icons.LocalSource import yokai.util.lang.getString /** @@ -607,9 +608,9 @@ open class BrowseSourceController(bundle: Bundle) : binding.emptyView.show( if (presenter.source is HttpSource) { - EmptyView.Image.Vector(Icons.Filled.ExploreOff) + Icons.Filled.ExploreOff } else { - EmptyView.Image.ResourceVector(R.drawable.ic_local_library_24dp) + Icons.Filled.LocalSource }, message, actions, diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt index 7a419059d3..c62ddbcaa8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/EmptyView.kt @@ -14,7 +14,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.AbstractComposeView import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.compose.ui.res.vectorResource import androidx.core.view.isVisible import dev.icerock.moko.resources.StringResource import eu.kanade.tachiyomi.util.isTablet @@ -28,7 +27,7 @@ class EmptyView @JvmOverloads constructor( defStyleAttr: Int = 0, ) : AbstractComposeView(context, attrs, defStyleAttr) { - private var image by mutableStateOf(Image.Vector(Icons.Filled.Download)) + private var image by mutableStateOf(Icons.Filled.Download) private var message by mutableStateOf("") private var actions by mutableStateOf(emptyList()) @@ -37,19 +36,11 @@ class EmptyView @JvmOverloads constructor( setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool) } - @Composable - fun image(): ImageVector { - return when (image) { - is Image.Vector -> (image as Image.Vector).image - is Image.ResourceVector -> ImageVector.vectorResource((image as Image.ResourceVector).id) - } - } - @Composable override fun Content() { YokaiTheme { EmptyScreen( - image = image(), + image = image, message = message, isTablet = isTablet(), actions = actions, @@ -69,7 +60,7 @@ class EmptyView @JvmOverloads constructor( * @param textResource text of information view */ fun show(image: ImageVector, textResource: StringResource, actions: List = emptyList()) { - show(Image.Vector(image), context.getString(textResource), actions) + show(image, context.getString(textResource), actions) } /** @@ -77,12 +68,7 @@ class EmptyView @JvmOverloads constructor( * @param textResource text of information view */ fun show(image: ImageVector, @StringRes textResource: Int, actions: List = emptyList()) { - show(Image.Vector(image), context.getString(textResource), actions) - } - - @Deprecated("Use EmptyView.Image instead of passing ImageVector directly") - fun show(image: ImageVector, message: String, actions: List = emptyList()) { - show(Image.Vector(image), message, actions) + show(image, context.getString(textResource), actions) } /** @@ -90,7 +76,7 @@ class EmptyView @JvmOverloads constructor( * @param drawable icon of information view * @param textResource text of information view */ - fun show(image: Image, message: String, actions: List = emptyList()) { + fun show(image: ImageVector, message: String, actions: List = emptyList()) { this.image = image this.message = message this.actions = actions @@ -101,9 +87,4 @@ class EmptyView @JvmOverloads constructor( val resId: StringResource, val listener: () -> Unit, ) - - sealed class Image { - data class Vector(val image: ImageVector) : Image() - data class ResourceVector(val id: Int) : Image() - } } diff --git a/app/src/main/java/yokai/presentation/component/icons/LocalSource.kt b/app/src/main/java/yokai/presentation/component/icons/LocalSource.kt new file mode 100644 index 0000000000..8d579b2cc1 --- /dev/null +++ b/app/src/main/java/yokai/presentation/component/icons/LocalSource.kt @@ -0,0 +1,39 @@ +package yokai.presentation.component.icons + +import androidx.compose.material.icons.Icons +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +private var _localSource: ImageVector? = null + +val Icons.Filled.LocalSource: ImageVector get() { + if (_localSource != null) return _localSource!! + _localSource = ImageVector.Builder( + name = "localSource", + defaultWidth = 24.0.dp, + defaultHeight = 24.0.dp, + viewportWidth = 24.0f, + viewportHeight = 24.0f, + ).apply { + path(fill = SolidColor(Color.Black)) { + moveTo(12f, 11.55f) + curveTo(9.64f, 9.35f, 6.48f, 8f, 3f, 8f) + verticalLineToRelative(11f) + curveToRelative(3.48f, 0f, 6.64f, 1.35f, 9f, 3.55f) + curveToRelative(2.36f, -2.19f, 5.52f, -3.55f, 9f, -3.55f) + verticalLineTo(8f) + curveToRelative(-3.48f, 0f, -6.64f, 1.35f, -9f, 3.55f) + close() + moveTo(12f, 8f) + curveToRelative(1.66f, 0f, 3f, -1.34f, 3f, -3f) + reflectiveCurveToRelative(-1.34f, -3f, -3f, -3f) + reflectiveCurveToRelative(-3f, 1.34f, -3f, 3f) + reflectiveCurveToRelative(1.34f, 3f, 3f, 3f) + close() + } + }.build() + return _localSource!! +} From 640feb69aca317c5eaa754145c735ea3ebf2757a Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 24 Dec 2024 08:07:10 +0700 Subject: [PATCH 013/181] fix(recents): Only set list on queue state change when it's not empty --- .../java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt index 24a5f4ca7c..322420ecc8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt @@ -113,9 +113,9 @@ class RecentsPresenter( } presenterScope.launchIO { downloadManager.queueState.collectLatest { - setDownloadedChapters(recentItems, it) + if (recentItems.isNotEmpty()) setDownloadedChapters(recentItems, it) withUIContext { - view?.showLists(recentItems, true) + if (recentItems.isNotEmpty()) view?.showLists(recentItems, true) view?.updateDownloadStatus(!downloadManager.isPaused()) } } From 677d96eed54e0d5c02d275e8a322486e553d7adf Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 24 Dec 2024 08:22:59 +0700 Subject: [PATCH 014/181] docs: Sync changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 921994ef66..371d1e44ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co ### Fixes - Allow users to bypass onboarding's permission step if Shizuku is installed +- Fix Recents page shows "No recent chapters" instead of a loading screen ### Other - Refactor Library to utilize Flow even more From 0049653355c4c20507d2f3d35b38bcfed37ba128 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 24 Dec 2024 08:50:53 +0700 Subject: [PATCH 015/181] chore: Remove ic_local_library_24dp No longer used, replaced by ImageVector --- app/src/main/res/drawable/ic_local_library_24dp.xml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_local_library_24dp.xml diff --git a/app/src/main/res/drawable/ic_local_library_24dp.xml b/app/src/main/res/drawable/ic_local_library_24dp.xml deleted file mode 100644 index 17aabbae06..0000000000 --- a/app/src/main/res/drawable/ic_local_library_24dp.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - From 031e30e227f5d7f975915d4c1a321c8aeb552d15 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 24 Dec 2024 12:19:24 +0700 Subject: [PATCH 016/181] refactor: Use Compose for reader chapter transition Co-authored-by: arkon --- .../ui/reader/loader/ArchivePageLoader.kt | 2 + .../ui/reader/loader/DirectoryPageLoader.kt | 2 + .../ui/reader/loader/DownloadPageLoader.kt | 2 + .../ui/reader/loader/EpubPageLoader.kt | 2 + .../ui/reader/loader/HttpPageLoader.kt | 8 +- .../tachiyomi/ui/reader/loader/PageLoader.kt | 2 + .../ui/reader/viewer/ReaderTransitionView.kt | 196 +++++------- .../tachiyomi/util/chapter/ChapterUtil.kt | 11 + .../presentation/reader/ChapterTransition.kt | 299 ++++++++++++++++++ 9 files changed, 398 insertions(+), 126 deletions(-) create mode 100644 app/src/main/java/yokai/presentation/reader/ChapterTransition.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt index ad78920502..dfa63cbba2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt @@ -11,6 +11,8 @@ import yokai.core.archive.ArchiveReader */ internal class ArchivePageLoader(private val reader: ArchiveReader) : PageLoader() { + override val isLocal: Boolean = true + /** * Recycles this loader and the open archive. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt index e6557c5a13..ba4a10bd6e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt @@ -11,6 +11,8 @@ import eu.kanade.tachiyomi.util.system.ImageUtil */ class DirectoryPageLoader(val file: UniFile) : PageLoader() { + override val isLocal: Boolean = true + /** * Returns the pages found on this directory ordered with a natural comparator. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt index f2c2ea8a46..6535ce9847 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt @@ -24,6 +24,8 @@ class DownloadPageLoader( private val downloadProvider: DownloadProvider, ) : PageLoader() { + override val isLocal: Boolean = true + // Needed to open input streams private val context: Application by injectLazy() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt index 53e0f26e74..0823453d1c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt @@ -10,6 +10,8 @@ import yokai.core.archive.ArchiveReader */ class EpubPageLoader(reader: ArchiveReader) : PageLoader() { + override val isLocal: Boolean = true + /** * The epub file. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt index 130a7fee57..ce32b60d34 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt @@ -8,6 +8,9 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.withIOContext +import java.util.concurrent.PriorityBlockingQueue +import java.util.concurrent.atomic.AtomicInteger +import kotlin.math.min import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -19,9 +22,6 @@ import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.suspendCancellableCoroutine import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.util.concurrent.* -import java.util.concurrent.atomic.* -import kotlin.math.min /** * Loader used to load chapters from an online source. @@ -33,6 +33,8 @@ class HttpPageLoader( private val preferences: PreferencesHelper = Injekt.get(), ) : PageLoader() { + override val isLocal: Boolean = false + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt index 720e81a43c..3362ff3caa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt @@ -9,6 +9,8 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage */ abstract class PageLoader { + abstract val isLocal: Boolean + /** * Whether this loader has been already recycled. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt index 3693274498..0e7b2602b8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt @@ -1,32 +1,31 @@ package eu.kanade.tachiyomi.ui.reader.viewer import android.content.Context -import android.text.SpannableStringBuilder -import android.text.style.ImageSpan import android.util.AttributeSet import android.view.LayoutInflater -import android.widget.LinearLayout -import androidx.annotation.ColorInt -import androidx.core.text.bold -import androidx.core.text.buildSpannedString -import androidx.core.text.inSpans -import androidx.core.view.isVisible -import eu.kanade.tachiyomi.R +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.AbstractComposeView import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition -import eu.kanade.tachiyomi.util.chapter.ChapterUtil.Companion.preferredChapterName -import eu.kanade.tachiyomi.util.system.contextCompatDrawable -import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.isLocal import uy.kohesive.injekt.injectLazy -import yokai.i18n.MR -import yokai.util.lang.getString -import kotlin.math.roundToInt +import yokai.presentation.reader.ChapterTransition +import yokai.presentation.theme.YokaiTheme class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - LinearLayout(context, attrs) { + AbstractComposeView(context, attrs) { + + private var data: Data? by mutableStateOf(null) private val binding: ReaderTransitionViewBinding = ReaderTransitionViewBinding.inflate(LayoutInflater.from(context), this, true) @@ -37,119 +36,70 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At } fun bind(transition: ChapterTransition, downloadManager: DownloadManager, manga: Manga?) { - manga ?: return - when (transition) { - is ChapterTransition.Prev -> bindPrevChapterTransition(transition, downloadManager, manga) - is ChapterTransition.Next -> bindNextChapterTransition(transition, downloadManager, manga) - } - - missingChapterWarning(transition) - } - - /** - * Binds a previous chapter transition on this view and subscribes to the page load status. - */ - private fun bindPrevChapterTransition( - transition: ChapterTransition, - downloadManager: DownloadManager, - manga: Manga, - ) { - val prevChapter = transition.to - - binding.lowerText.isVisible = prevChapter != null - if (prevChapter != null) { - binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START - val isPrevDownloaded = downloadManager.isChapterDownloaded(prevChapter.chapter, manga) - val isCurrentDownloaded = downloadManager.isChapterDownloaded(transition.from.chapter, manga) - binding.upperText.text = buildSpannedString { - bold { append(context.getString(MR.strings.previous_title)) } - append("\n${prevChapter.chapter.preferredChapterName(context, manga, preferences)}") - if (isPrevDownloaded != isCurrentDownloaded) addDLImageSpan(isPrevDownloaded) - } - binding.lowerText.text = buildSpannedString { - bold { append(context.getString(MR.strings.current_chapter)) } - val name = transition.from.chapter.preferredChapterName(context, manga, preferences) - append("\n$name") - } + data = if (manga != null) { + Data( + manga = manga, + transition = transition, + currChapterDownloaded = transition.from.pageLoader?.isLocal == true, + goingToChapterDownloaded = manga.isLocal() || + transition.to?.chapter?.let { goingToChapter -> + downloadManager.isChapterDownloaded( + chapter = goingToChapter, + manga = manga, + skipCache = true, + ) + } ?: false, + ) } else { - binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER - binding.upperText.text = context.getString(MR.strings.theres_no_previous_chapter) + null } } - /** - * Binds a next chapter transition on this view and subscribes to the load status. - */ - private fun bindNextChapterTransition( - transition: ChapterTransition, - downloadManager: DownloadManager, - manga: Manga, - ) { - val nextChapter = transition.to - - binding.lowerText.isVisible = nextChapter != null - if (nextChapter != null) { - binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START - val isCurrentDownloaded = downloadManager.isChapterDownloaded(transition.from.chapter, manga) - val isNextDownloaded = downloadManager.isChapterDownloaded(nextChapter.chapter, manga) - binding.upperText.text = buildSpannedString { - bold { append(context.getString(MR.strings.finished_chapter)) } - val name = transition.from.chapter.preferredChapterName(context, manga, preferences) - append("\n$name") + @Composable + override fun Content() { + data?.let { + YokaiTheme { + CompositionLocalProvider ( + LocalTextStyle provides MaterialTheme.typography.bodySmall, + LocalContentColor provides MaterialTheme.colorScheme.onBackground, + ) { + ChapterTransition( + manga = it.manga, + transition = it.transition, + currChapterDownloaded = it.currChapterDownloaded, + goingToChapterDownloaded = it.goingToChapterDownloaded, + ) + } } - binding.lowerText.text = buildSpannedString { - bold { append(context.getString(MR.strings.next_title)) } - append("\n${nextChapter.chapter.preferredChapterName(context, manga, preferences)}") - if (isNextDownloaded != isCurrentDownloaded) addDLImageSpan(isNextDownloaded) - } - } else { - binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER - binding.upperText.text = context.getString(MR.strings.theres_no_next_chapter) } } - private fun SpannableStringBuilder.addDLImageSpan(isDownloaded: Boolean) { - val icon = context.contextCompatDrawable( - if (isDownloaded) R.drawable.ic_file_download_24dp else R.drawable.ic_cloud_24dp, - ) - ?.mutate() - ?.apply { - val size = binding.lowerText.textSize + 4f.dpToPx - setTint(binding.lowerText.currentTextColor) - setBounds(0, 0, size.roundToInt(), size.roundToInt()) - } ?: return - append(" ") - inSpans(ImageSpan(icon)) { append("image") } - } - - fun setTextColors(@ColorInt color: Int) { - binding.upperText.setTextColor(color) - binding.warningText.setTextColor(color) - binding.lowerText.setTextColor(color) - } - - private fun missingChapterWarning(transition: ChapterTransition) { - if (transition.to == null) { - binding.warning.isVisible = false - return - } - - val hasMissingChapters = when (transition) { - is ChapterTransition.Prev -> hasMissingChapters(transition.from, transition.to) - is ChapterTransition.Next -> hasMissingChapters(transition.to, transition.from) - } - - if (!hasMissingChapters) { - binding.warning.isVisible = false - return - } - - val chapterDifference = when (transition) { - is ChapterTransition.Prev -> calculateChapterDifference(transition.from, transition.to) - is ChapterTransition.Next -> calculateChapterDifference(transition.to, transition.from) - } - - binding.warningText.text = context.getString(MR.plurals.missing_chapters_warning, chapterDifference.toInt(), chapterDifference.toInt()) - binding.warning.isVisible = true - } + private data class Data( + val manga: Manga, + val transition: ChapterTransition, + val currChapterDownloaded: Boolean, + val goingToChapterDownloaded: Boolean, + ) +} + +fun missingChapterCount(transition: ChapterTransition): Int { + if (transition.to == null) { + return 0 + } + + val hasMissingChapters = when (transition) { + is ChapterTransition.Prev -> hasMissingChapters(transition.from, transition.to) + is ChapterTransition.Next -> hasMissingChapters(transition.to, transition.from) + } + + if (!hasMissingChapters) { + return 0 + } + + val chapterDifference = when (transition) { + is ChapterTransition.Prev -> calculateChapterDifference(transition.from, transition.to) + is ChapterTransition.Next -> calculateChapterDifference(transition.to, transition.from) + } + + return chapterDifference.toInt() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterUtil.kt index d5bd0ac97c..33d9efdb30 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterUtil.kt @@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.util.chapter import android.content.Context import android.content.res.ColorStateList import android.widget.TextView +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext import androidx.core.graphics.ColorUtils import androidx.core.widget.TextViewCompat import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat @@ -19,6 +21,8 @@ import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.timeSpanFromNow import java.text.DecimalFormat import java.text.DecimalFormatSymbols +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import yokai.i18n.MR import yokai.util.lang.getString @@ -182,5 +186,12 @@ class ChapterUtil { name } } + + @Composable + fun Chapter.preferredChapterName(manga: Manga): String { + val preferences: PreferencesHelper = Injekt.get() + val context = LocalContext.current + return preferredChapterName(context, manga, preferences) + } } } diff --git a/app/src/main/java/yokai/presentation/reader/ChapterTransition.kt b/app/src/main/java/yokai/presentation/reader/ChapterTransition.kt new file mode 100644 index 0000000000..a6c1c403f7 --- /dev/null +++ b/app/src/main/java/yokai/presentation/reader/ChapterTransition.kt @@ -0,0 +1,299 @@ +package yokai.presentation.reader + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.text.InlineTextContent +import androidx.compose.foundation.text.appendInlineContent +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.Cloud +import androidx.compose.material.icons.outlined.Info +import androidx.compose.material.icons.outlined.Warning +import androidx.compose.material3.CardColors +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.Placeholder +import androidx.compose.ui.text.PlaceholderVerticalAlign +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dev.icerock.moko.resources.compose.pluralStringResource +import dev.icerock.moko.resources.compose.stringResource +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.domain.manga.models.Manga +import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition +import eu.kanade.tachiyomi.ui.reader.viewer.missingChapterCount +import eu.kanade.tachiyomi.util.chapter.ChapterUtil.Companion.preferredChapterName +import kotlinx.collections.immutable.persistentMapOf +import yokai.i18n.MR +import yokai.presentation.core.util.secondaryItemAlpha + +@Composable +fun ChapterTransition( + manga: Manga, + transition: ChapterTransition, + currChapterDownloaded: Boolean, + goingToChapterDownloaded: Boolean, +) { + val currChapter = transition.from.chapter + val goingToChapter = transition.to?.chapter + val chapterGap = missingChapterCount(transition) + + ProvideTextStyle(MaterialTheme.typography.bodyMedium) { + when (transition) { + is ChapterTransition.Prev -> { + TransitionText( + manga = manga, + topLabel = stringResource(MR.strings.previous_title), + topChapter = goingToChapter, + topChapterDownloaded = goingToChapterDownloaded, + bottomLabel = stringResource(MR.strings.current_chapter), + bottomChapter = currChapter, + bottomChapterDownloaded = currChapterDownloaded, + fallbackLabel = stringResource(MR.strings.theres_no_previous_chapter), + chapterGap = chapterGap, + ) + } + is ChapterTransition.Next -> { + TransitionText( + manga = manga, + topLabel = stringResource(MR.strings.finished_chapter), + topChapter = currChapter, + topChapterDownloaded = currChapterDownloaded, + bottomLabel = stringResource(MR.strings.next_title), + bottomChapter = goingToChapter, + bottomChapterDownloaded = goingToChapterDownloaded, + fallbackLabel = stringResource(MR.strings.theres_no_next_chapter), + chapterGap = chapterGap, + ) + } + } + } +} + +@Composable +private fun TransitionText( + manga: Manga, + topLabel: String, + topChapter: Chapter?, + topChapterDownloaded: Boolean, + bottomLabel: String, + bottomChapter: Chapter?, + bottomChapterDownloaded: Boolean, + fallbackLabel: String, + chapterGap: Int, +) { + Column ( + modifier = Modifier + .widthIn(max = 460.dp) + .fillMaxWidth(), + ) { + if (topChapter != null) { + ChapterText( + header = topLabel, + name = topChapter.preferredChapterName(manga), + scanlator = topChapter.scanlator, + otherDownloaded = bottomChapterDownloaded, + downloaded = topChapterDownloaded, + ) + + Spacer(Modifier.height(VerticalSpacerSize)) + } else { + NoChapterNotification( + text = fallbackLabel, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + } + + if (bottomChapter != null) { + if (chapterGap > 0) { + ChapterGapWarning( + gapCount = chapterGap, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + } + + Spacer(Modifier.height(VerticalSpacerSize)) + + ChapterText( + header = bottomLabel, + name = bottomChapter.preferredChapterName(manga), + scanlator = bottomChapter.scanlator, + otherDownloaded = topChapterDownloaded, + downloaded = bottomChapterDownloaded, + ) + } else { + NoChapterNotification( + text = fallbackLabel, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + } + } +} + +@Composable +private fun NoChapterNotification( + text: String, + modifier: Modifier = Modifier, +) { + OutlinedCard ( + modifier = modifier, + colors = CardColor, + ) { + Row ( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Outlined.Info, + tint = MaterialTheme.colorScheme.primary, + contentDescription = null, + ) + + Text( + text = text, + style = MaterialTheme.typography.bodyMedium, + ) + } + } +} + +@Composable +private fun ChapterGapWarning( + gapCount: Int, + modifier: Modifier = Modifier, +) { + OutlinedCard( + modifier = modifier, + colors = CardColor, + ) { + Row( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Outlined.Warning, + tint = MaterialTheme.colorScheme.error, + contentDescription = null, + ) + + Text( + text = pluralStringResource(MR.plurals.missing_chapters_warning, quantity = gapCount, gapCount), + style = MaterialTheme.typography.bodyMedium, + ) + } + } +} + +@Composable +private fun ChapterHeaderText( + text: String, + modifier: Modifier = Modifier, +) { + Text( + text = text, + modifier = modifier, + style = MaterialTheme.typography.titleMedium, + ) +} + +@Composable +private fun ChapterText( + header: String, + name: String, + scanlator: String?, + otherDownloaded: Boolean, + downloaded: Boolean, +) { + Column { + ChapterHeaderText( + text = header, + modifier = Modifier.padding(bottom = 4.dp), + ) + + Text( + text = buildAnnotatedString { + if (downloaded || otherDownloaded) { + if (downloaded) { + appendInlineContent(DOWNLOADED_ICON_ID) + } else { + appendInlineContent(ONLINE_ICON_ID) + } + append(' ') + } + append(name) + }, + fontSize = 20.sp, + maxLines = 5, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleLarge, + inlineContent = persistentMapOf( + DOWNLOADED_ICON_ID to InlineTextContent( + Placeholder( + width = 22.sp, + height = 22.sp, + placeholderVerticalAlign = PlaceholderVerticalAlign.Center, + ), + ) { + Icon( + imageVector = Icons.Filled.CheckCircle, + contentDescription = stringResource(MR.strings.downloaded), + ) + }, + ONLINE_ICON_ID to InlineTextContent( + Placeholder( + width = 22.sp, + height = 22.sp, + placeholderVerticalAlign = PlaceholderVerticalAlign.Center, + ), + ) { + Icon( + imageVector = Icons.Filled.Cloud, + contentDescription = stringResource(MR.strings.not_downloaded), + ) + }, + ), + ) + + scanlator?.let { + Text( + text = it, + modifier = Modifier + .secondaryItemAlpha() + .padding(top = 2.dp), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + ) + } + } +} + +private val CardColor: CardColors + @Composable + get() = CardDefaults.outlinedCardColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onSurface, + ) + +private val VerticalSpacerSize = 24.dp +private const val DOWNLOADED_ICON_ID = "downloaded" +private const val ONLINE_ICON_ID = "online" From f9bb2b96cb43aa6da17fa31d225be10440433e36 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 24 Dec 2024 12:43:30 +0700 Subject: [PATCH 017/181] fix(reader): Fix build --- .../ui/reader/viewer/ReaderTransitionView.kt | 16 +++++-------- .../viewer/pager/PagerTransitionHolder.kt | 11 +-------- .../viewer/webtoon/WebtoonTransitionHolder.kt | 11 +-------- .../kanade/tachiyomi/util/system/ThemeUtil.kt | 23 +++++++++++-------- 4 files changed, 21 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt index 0e7b2602b8..75eab36d98 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.reader.viewer import android.content.Context import android.util.AttributeSet -import android.view.LayoutInflater import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme @@ -13,12 +12,10 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.platform.AbstractComposeView import eu.kanade.tachiyomi.data.download.DownloadManager -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.util.isLocal -import uy.kohesive.injekt.injectLazy +import eu.kanade.tachiyomi.util.system.ThemeUtil import yokai.presentation.reader.ChapterTransition import yokai.presentation.theme.YokaiTheme @@ -27,17 +24,14 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At private var data: Data? by mutableStateOf(null) - private val binding: ReaderTransitionViewBinding = - ReaderTransitionViewBinding.inflate(LayoutInflater.from(context), this, true) - private val preferences: PreferencesHelper by injectLazy() - init { layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) } - fun bind(transition: ChapterTransition, downloadManager: DownloadManager, manga: Manga?) { + fun bind(theme: Int, transition: ChapterTransition, downloadManager: DownloadManager, manga: Manga?) { data = if (manga != null) { Data( + theme = theme, manga = manga, transition = transition, currChapterDownloaded = transition.from.pageLoader?.isLocal == true, @@ -58,10 +52,11 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At @Composable override fun Content() { data?.let { + val contentColor = ThemeUtil.readerContentColor(it.theme, MaterialTheme.colorScheme.onBackground) YokaiTheme { CompositionLocalProvider ( LocalTextStyle provides MaterialTheme.typography.bodySmall, - LocalContentColor provides MaterialTheme.colorScheme.onBackground, + LocalContentColor provides contentColor, ) { ChapterTransition( manga = it.manga, @@ -75,6 +70,7 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At } private data class Data( + val theme: Int, val manga: Manga, val transition: ChapterTransition, val currChapterDownloaded: Boolean, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt index c2854b2c0d..7114bbc499 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt @@ -14,14 +14,11 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.view.updatePaddingRelative -import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.viewer.ReaderButton import eu.kanade.tachiyomi.ui.reader.viewer.ReaderTransitionView -import eu.kanade.tachiyomi.util.system.ThemeUtil import eu.kanade.tachiyomi.util.system.dpToPx -import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.view.setText import eu.kanade.tachiyomi.widget.ViewPagerAdapter import kotlinx.coroutines.Job @@ -67,17 +64,11 @@ class PagerTransitionHolder( setPadding(sidePadding, 0, sidePadding, 0) val transitionView = ReaderTransitionView(context) - transitionView.setTextColors( - ThemeUtil.readerContentColor( - viewer.config.readerTheme, - context.getResourceColor(R.attr.colorOnBackground), - ) - ) addView(transitionView) addView(pagesContainer) - transitionView.bind(transition, viewer.downloadManager, viewer.activity.viewModel.manga) + transitionView.bind(viewer.config.readerTheme, transition, viewer.downloadManager, viewer.activity.viewModel.manga) transition.to?.let { observeStatus(it) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt index 1fb8d78df2..cfce248456 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonTransitionHolder.kt @@ -12,13 +12,10 @@ import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.view.isNotEmpty import androidx.core.view.isVisible -import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.viewer.ReaderTransitionView -import eu.kanade.tachiyomi.util.system.ThemeUtil import eu.kanade.tachiyomi.util.system.dpToPx -import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.view.setText import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope @@ -72,13 +69,7 @@ class WebtoonTransitionHolder( * Binds the given [transition] with this view holder, subscribing to its state. */ fun bind(transition: ChapterTransition) { - transitionView.setTextColors( - ThemeUtil.readerContentColor( - viewer.config.readerTheme, - context.getResourceColor(R.attr.colorOnBackground), - ) - ) - transitionView.bind(transition, viewer.downloadManager, viewer.activity.viewModel.manga) + transitionView.bind(viewer.config.readerTheme, transition, viewer.downloadManager, viewer.activity.viewModel.manga) transition.to?.let { observeStatus(it, transition) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ThemeUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ThemeUtil.kt index 83ce972fe8..cb5ec8e234 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ThemeUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ThemeUtil.kt @@ -2,9 +2,10 @@ package eu.kanade.tachiyomi.util.system import android.content.Context import android.content.res.Resources -import android.graphics.Color import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color import androidx.core.content.edit import androidx.core.view.WindowInsetsControllerCompat import androidx.preference.PreferenceManager @@ -13,6 +14,7 @@ import eu.kanade.tachiyomi.data.preference.PreferenceKeys import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.reader.settings.ReaderBackgroundColor import uy.kohesive.injekt.injectLazy +import android.graphics.Color as AColor object ThemeUtil { @@ -47,20 +49,21 @@ object ThemeUtil { return context.isInNightMode() && preferences.themeDarkAmoled().get() } - fun readerBackgroundColor(theme: Int, default: Int = Color.WHITE): Int { + fun readerBackgroundColor(theme: Int, default: Int = AColor.WHITE): Int { return when (ReaderBackgroundColor.fromPreference(theme)) { - ReaderBackgroundColor.GRAY -> Color.rgb(32, 33, 37) - ReaderBackgroundColor.BLACK -> Color.BLACK - ReaderBackgroundColor.WHITE -> Color.WHITE + ReaderBackgroundColor.GRAY -> AColor.rgb(32, 33, 37) + ReaderBackgroundColor.BLACK -> AColor.BLACK + ReaderBackgroundColor.WHITE -> AColor.WHITE else -> default } } - fun readerContentColor(theme: Int, default: Int = Color.BLACK): Int { + @Composable + fun readerContentColor(theme: Int, default: Color = Color.Black): Color { return when (ReaderBackgroundColor.fromPreference(theme)) { - ReaderBackgroundColor.GRAY -> Color.WHITE - ReaderBackgroundColor.BLACK -> Color.WHITE - ReaderBackgroundColor.WHITE -> Color.BLACK + ReaderBackgroundColor.GRAY -> Color.White + ReaderBackgroundColor.BLACK -> Color.White + ReaderBackgroundColor.WHITE -> Color.Black else -> default } } @@ -78,7 +81,7 @@ fun AppCompatActivity.getThemeWithExtras(theme: Resources.Theme, preferences: Pr if (oldTheme != null && useAmoled) { val array = oldTheme.obtainStyledAttributes(intArrayOf(R.attr.background)) val bg = array.getColor(0, 0) - if (bg == Color.BLACK) { + if (bg == AColor.BLACK) { return oldTheme } } From a853deae4ad320399d0061a23951d8c6976dcffe Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 24 Dec 2024 12:58:39 +0700 Subject: [PATCH 018/181] fix(reader): Use it directly --- .../kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt index 75eab36d98..109d260dd3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt @@ -52,11 +52,10 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At @Composable override fun Content() { data?.let { - val contentColor = ThemeUtil.readerContentColor(it.theme, MaterialTheme.colorScheme.onBackground) YokaiTheme { CompositionLocalProvider ( LocalTextStyle provides MaterialTheme.typography.bodySmall, - LocalContentColor provides contentColor, + LocalContentColor provides ThemeUtil.readerContentColor(it.theme, MaterialTheme.colorScheme.onBackground), ) { ChapterTransition( manga = it.manga, From 6d2ae386a23912e1c7b1fe9ed8efad0f2c9ee25b Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 24 Dec 2024 13:41:37 +0700 Subject: [PATCH 019/181] fix(reader): Use LocalContentColor's value --- .../main/java/yokai/presentation/reader/ChapterTransition.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/yokai/presentation/reader/ChapterTransition.kt b/app/src/main/java/yokai/presentation/reader/ChapterTransition.kt index a6c1c403f7..ef6c0e3156 100644 --- a/app/src/main/java/yokai/presentation/reader/ChapterTransition.kt +++ b/app/src/main/java/yokai/presentation/reader/ChapterTransition.kt @@ -18,6 +18,7 @@ import androidx.compose.material.icons.outlined.Warning import androidx.compose.material3.CardColors import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedCard import androidx.compose.material3.ProvideTextStyle @@ -291,7 +292,7 @@ private val CardColor: CardColors @Composable get() = CardDefaults.outlinedCardColors( containerColor = Color.Transparent, - contentColor = MaterialTheme.colorScheme.onSurface, + contentColor = LocalContentColor.current, ) private val VerticalSpacerSize = 24.dp From 915debd41b710e985b68bde3b289954bcf4a0da0 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 24 Dec 2024 14:00:19 +0700 Subject: [PATCH 020/181] docs: Sync changelog [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371d1e44ea..4e7a83e792 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Refactor Library to utilize Flow even more - Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.1 - Refactor EmptyView to use Compose +- Refactor Reader ChapterTransition to use Compose ## [1.9.7] From b201e410a360bee44cd4f7c26fd511094ba51886 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 25 Dec 2024 07:24:22 +0700 Subject: [PATCH 021/181] chore: Remove deprecated constants They're moved to Constants object --- .../tachiyomi/appwidget/components/LockedWidget.kt | 6 +++--- .../tachiyomi/appwidget/components/UpdatesWidget.kt | 7 +++---- .../data/notification/NotificationReceiver.kt | 2 +- .../java/eu/kanade/tachiyomi/ui/main/MainActivity.kt | 12 ++---------- .../eu/kanade/tachiyomi/ui/main/SearchActivity.kt | 2 +- 5 files changed, 10 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt index 7b89f51a37..56a49cd16d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/LockedWidget.kt @@ -17,15 +17,15 @@ import androidx.glance.text.TextAlign import androidx.glance.text.TextStyle import androidx.glance.unit.ColorProvider import eu.kanade.tachiyomi.R -import yokai.i18n.MR import eu.kanade.tachiyomi.appwidget.ContainerModifier import eu.kanade.tachiyomi.appwidget.util.stringResource -import eu.kanade.tachiyomi.ui.main.MainActivity +import yokai.i18n.MR +import yokai.presentation.core.Constants @Composable fun LockedWidget() { val context = LocalContext.current - val intent = Intent(LocalContext.current, Class.forName(MainActivity.MAIN_ACTIVITY)).apply { + val intent = Intent(LocalContext.current, Class.forName(Constants.MAIN_ACTIVITY)).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } Box( diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt index d9786f70ad..542b76d3fc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/components/UpdatesWidget.kt @@ -17,19 +17,18 @@ import androidx.glance.layout.Row import androidx.glance.layout.fillMaxWidth import androidx.glance.layout.padding import androidx.glance.text.Text -import eu.kanade.tachiyomi.R -import yokai.i18n.MR -import yokai.util.lang.getString import eu.kanade.tachiyomi.appwidget.ContainerModifier import eu.kanade.tachiyomi.appwidget.util.calculateRowAndColumnCount import eu.kanade.tachiyomi.appwidget.util.stringResource import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.SearchActivity +import yokai.i18n.MR +import yokai.presentation.core.Constants @Composable fun UpdatesWidget(data: List>?) { val (rowCount, columnCount) = LocalSize.current.calculateRowAndColumnCount() - val mainIntent = Intent(LocalContext.current, MainActivity::class.java).setAction(MainActivity.SHORTCUT_RECENTS) + val mainIntent = Intent(LocalContext.current, MainActivity::class.java).setAction(Constants.SHORTCUT_RECENTS) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) Column( modifier = ContainerModifier.clickable(actionStartActivity(mainIntent)), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 649ead87a9..57af3da28a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -487,7 +487,7 @@ class NotificationReceiver : BroadcastReceiver() { */ internal fun openChapterPendingActivity(context: Context, manga: Manga, groupId: Int): PendingIntent { val newIntent = - Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA) + Intent(context, MainActivity::class.java).setAction(Constants.SHORTCUT_MANGA) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) .putExtra(Constants.MANGA_EXTRA, manga.id) .putExtra("notificationId", manga.id.hashCode()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 570db5e7ac..67fb31da3e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -1053,13 +1053,13 @@ open class MainActivity : BaseActivity() { } when (intent.action) { SHORTCUT_LIBRARY -> nav.selectedItemId = R.id.nav_library - SHORTCUT_RECENTLY_UPDATED, SHORTCUT_RECENTLY_READ, SHORTCUT_RECENTS -> { + SHORTCUT_RECENTLY_UPDATED, SHORTCUT_RECENTLY_READ, Constants.SHORTCUT_RECENTS -> { if (nav.selectedItemId != R.id.nav_recents) { nav.selectedItemId = R.id.nav_recents } else { router.popToRoot() } - if (intent.action == SHORTCUT_RECENTS) return true + if (intent.action == Constants.SHORTCUT_RECENTS) return true nav.post { val controller = router.backstack.firstOrNull()?.controller as? RecentsController @@ -1609,20 +1609,12 @@ open class MainActivity : BaseActivity() { private const val SWIPE_THRESHOLD = 100 private const val SWIPE_VELOCITY_THRESHOLD = 100 - const val MAIN_ACTIVITY = Constants.MAIN_ACTIVITY - // Shortcut actions const val SHORTCUT_LIBRARY = "eu.kanade.tachiyomi.SHOW_LIBRARY" - @Deprecated("Use the one from Constants object instead") - const val SHORTCUT_RECENTS = Constants.SHORTCUT_RECENTS const val SHORTCUT_RECENTLY_UPDATED = "eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED" const val SHORTCUT_RECENTLY_READ = "eu.kanade.tachiyomi.SHOW_RECENTLY_READ" const val SHORTCUT_BROWSE = "eu.kanade.tachiyomi.SHOW_BROWSE" const val SHORTCUT_DOWNLOADS = "eu.kanade.tachiyomi.SHOW_DOWNLOADS" - @Deprecated("Use the one from Constants object instead") - const val SHORTCUT_MANGA = Constants.SHORTCUT_MANGA - @Deprecated("Use the one from Constants object instead") - const val SHORTCUT_MANGA_BACK = Constants.SHORTCUT_MANGA_BACK const val SHORTCUT_UPDATE_NOTES = "eu.kanade.tachiyomi.SHOW_UPDATE_NOTES" const val SHORTCUT_SOURCE = "eu.kanade.tachiyomi.SHOW_SOURCE" const val SHORTCUT_READER_SETTINGS = "eu.kanade.tachiyomi.READER_SETTINGS" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt index 894a5468e7..9a01c3873c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt @@ -97,7 +97,7 @@ class SearchActivity : MainActivity() { } private fun intentShouldGoBack() = - intent.action in listOf(SHORTCUT_MANGA, SHORTCUT_READER_SETTINGS, SHORTCUT_BROWSE) + intent.action in listOf(Constants.SHORTCUT_MANGA, SHORTCUT_READER_SETTINGS, SHORTCUT_BROWSE) override fun syncActivityViewWithController( to: Controller?, From c09c4045e2c8d8531e21a3b996948eb2d5ac30b9 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 25 Dec 2024 08:46:48 +0700 Subject: [PATCH 022/181] refactor: Rework buildSrc Co-authored-by: AntsyLich <59261191+antsylich@users.noreply.github.com> --- app/build.gradle.kts | 22 +---- build.gradle.kts | 46 ---------- buildSrc/build.gradle.kts | 17 +++- buildSrc/settings.gradle.kts | 17 ++++ buildSrc/src/main/kotlin/AndroidConfig.kt | 11 ++- ...kai.android.application.compose.gradle.kts | 10 ++ .../yokai.android.application.gradle.kts | 15 +++ .../yokai.android.library.compose.gradle.kts | 9 ++ .../kotlin/yokai.android.library.gradle.kts | 11 +++ .../kotlin/yokai/build/ProjectExtensions.kt | 92 +++++++++++++++++++ core/build.gradle.kts | 4 +- data/build.gradle.kts | 4 +- domain/build.gradle.kts | 4 +- gradle/androidx.versions.toml | 2 + gradle/kotlinx.versions.toml | 5 +- i18n/build.gradle.kts | 4 +- presentation/core/build.gradle.kts | 21 ++++- presentation/widget/build.gradle.kts | 6 +- source/api/build.gradle.kts | 4 +- 19 files changed, 218 insertions(+), 86 deletions(-) create mode 100644 buildSrc/src/main/kotlin/yokai.android.application.compose.gradle.kts create mode 100644 buildSrc/src/main/kotlin/yokai.android.application.gradle.kts create mode 100644 buildSrc/src/main/kotlin/yokai.android.library.compose.gradle.kts create mode 100644 buildSrc/src/main/kotlin/yokai.android.library.gradle.kts diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 00ba2c765b..a0f66ecfda 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -7,9 +7,8 @@ import java.time.format.DateTimeFormatter import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - alias(androidx.plugins.application) - alias(kotlinx.plugins.android) - alias(kotlinx.plugins.compose.compiler) + id("yokai.android.application") + id("yokai.android.application.compose") alias(kotlinx.plugins.serialization) alias(kotlinx.plugins.parcelize) alias(libs.plugins.aboutlibraries) @@ -122,7 +121,6 @@ android { buildFeatures { viewBinding = true - compose = true // If you're here because there's not BuildConfig, build the app first, it'll generate it for you buildConfig = true @@ -284,13 +282,12 @@ tasks { // "-opt-in=kotlin.Experimental", "-opt-in=kotlin.RequiresOptIn", "-opt-in=kotlin.ExperimentalStdlibApi", + "-opt-in=coil3.annotation.ExperimentalCoilApi", "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi", "-opt-in=androidx.compose.animation.ExperimentalAnimationApi", - // "-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi", - "-opt-in=coil3.annotation.ExperimentalCoilApi", // "-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=kotlinx.coroutines.FlowPreview", @@ -298,19 +295,6 @@ tasks { "-opt-in=kotlinx.coroutines.InternalCoroutinesApi", "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", ) - - if (project.findProperty("tachiyomi.enableComposeCompilerMetrics") == "true") { - compilerOptions.freeCompilerArgs.addAll( - "-P", - "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + - (project.layout.buildDirectory.asFile.orNull?.absolutePath ?: "/tmp/yokai") + "/compose_metrics", - ) - compilerOptions.freeCompilerArgs.addAll( - "-P", - "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + - (project.layout.buildDirectory.asFile.orNull?.absolutePath ?: "/tmp/yokai") + "/compose_metrics", - ) - } } // Duplicating Hebrew string assets due to some locale code issues on different devices diff --git a/build.gradle.kts b/build.gradle.kts index b85331dcda..d5c90cc3b8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,12 +8,6 @@ import java.util.* plugins { alias(libs.plugins.kotlinter) alias(libs.plugins.gradle.versions) - alias(androidx.plugins.application) apply false - alias(androidx.plugins.library) apply false - alias(kotlinx.plugins.android) apply false - alias(kotlinx.plugins.compose.compiler) apply false - alias(kotlinx.plugins.multiplatform) apply false - alias(kotlinx.plugins.parcelize) apply false alias(kotlinx.plugins.serialization) apply false alias(libs.plugins.aboutlibraries) apply false alias(libs.plugins.firebase.crashlytics) apply false @@ -22,46 +16,6 @@ plugins { alias(libs.plugins.sqldelight) apply false } -subprojects { - tasks.withType { - compilerOptions { - jvmTarget = JvmTarget.JVM_17 - } - } - - tasks.withType { - useJUnitPlatform() - testLogging { - events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) - } - } - - plugins.withType { - configure { - compileSdkVersion(AndroidConfig.compileSdk) - ndkVersion = AndroidConfig.ndk - - defaultConfig { - minSdk = AndroidConfig.minSdk - targetSdk = AndroidConfig.targetSdk - ndk { - version = AndroidConfig.ndk - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - isCoreLibraryDesugaringEnabled = true - } - - dependencies { - add("coreLibraryDesugaring", libs.desugar) - } - } - } -} - tasks.named("dependencyUpdates", com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask::class.java).configure { rejectVersionIf { val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { candidate.version.uppercase(Locale.ROOT).contains(it) } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 88cd14f786..b825b41720 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,6 +1,21 @@ plugins { `kotlin-dsl` } + +dependencies { + implementation(androidx.gradle) + implementation(kotlinx.gradle) + implementation(kotlinx.compose.compiler.gradle) + implementation(gradleApi()) + + implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) + implementation(files(androidx.javaClass.superclass.protectionDomain.codeSource.location)) + implementation(files(compose.javaClass.superclass.protectionDomain.codeSource.location)) + implementation(files(kotlinx.javaClass.superclass.protectionDomain.codeSource.location)) +} + repositories { + gradlePluginPortal() mavenCentral() -} \ No newline at end of file + google() +} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts index 5b387ffc76..df8c5a31ee 100644 --- a/buildSrc/settings.gradle.kts +++ b/buildSrc/settings.gradle.kts @@ -1 +1,18 @@ +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + create("androidx") { + from(files("../gradle/androidx.versions.toml")) + } + create("compose") { + from(files("../gradle/compose.versions.toml")) + } + create("kotlinx") { + from(files("../gradle/kotlinx.versions.toml")) + } + } +} + rootProject.name = "yokai-buildSrc" diff --git a/buildSrc/src/main/kotlin/AndroidConfig.kt b/buildSrc/src/main/kotlin/AndroidConfig.kt index 742ae66247..025e0fd522 100644 --- a/buildSrc/src/main/kotlin/AndroidConfig.kt +++ b/buildSrc/src/main/kotlin/AndroidConfig.kt @@ -1,6 +1,9 @@ +import org.gradle.api.JavaVersion as GradleJavaVersion + object AndroidConfig { - const val compileSdk = 35 - const val minSdk = 23 - const val targetSdk = 35 - const val ndk = "27.2.12479018" + const val COMPILE_SDK = 35 + const val MIN_SDK = 23 + const val TARGET_SDK = 35 + const val NDK = "27.2.12479018" + val JavaVersion = GradleJavaVersion.VERSION_17 } diff --git a/buildSrc/src/main/kotlin/yokai.android.application.compose.gradle.kts b/buildSrc/src/main/kotlin/yokai.android.application.compose.gradle.kts new file mode 100644 index 0000000000..3bb076cfe8 --- /dev/null +++ b/buildSrc/src/main/kotlin/yokai.android.application.compose.gradle.kts @@ -0,0 +1,10 @@ +import yokai.build.configureCompose + +plugins { + id("com.android.application") + kotlin("android") +} + +android { + configureCompose(this) +} diff --git a/buildSrc/src/main/kotlin/yokai.android.application.gradle.kts b/buildSrc/src/main/kotlin/yokai.android.application.gradle.kts new file mode 100644 index 0000000000..70f34fcad5 --- /dev/null +++ b/buildSrc/src/main/kotlin/yokai.android.application.gradle.kts @@ -0,0 +1,15 @@ +import yokai.build.configureAndroid +import yokai.build.configureTest + +plugins { + id("com.android.application") + kotlin("android") +} + +android { + defaultConfig { + targetSdk = AndroidConfig.TARGET_SDK + } + configureAndroid(this) + configureTest() +} diff --git a/buildSrc/src/main/kotlin/yokai.android.library.compose.gradle.kts b/buildSrc/src/main/kotlin/yokai.android.library.compose.gradle.kts new file mode 100644 index 0000000000..fed5d565de --- /dev/null +++ b/buildSrc/src/main/kotlin/yokai.android.library.compose.gradle.kts @@ -0,0 +1,9 @@ +import yokai.build.configureCompose + +plugins { + id("com.android.library") +} + +android { + configureCompose(this) +} diff --git a/buildSrc/src/main/kotlin/yokai.android.library.gradle.kts b/buildSrc/src/main/kotlin/yokai.android.library.gradle.kts new file mode 100644 index 0000000000..7dd7d66697 --- /dev/null +++ b/buildSrc/src/main/kotlin/yokai.android.library.gradle.kts @@ -0,0 +1,11 @@ +import yokai.build.configureAndroid +import yokai.build.configureTest + +plugins { + id("com.android.library") +} + +android { + configureAndroid(this) + configureTest() +} diff --git a/buildSrc/src/main/kotlin/yokai/build/ProjectExtensions.kt b/buildSrc/src/main/kotlin/yokai/build/ProjectExtensions.kt index df362eaefc..cee0c412ac 100644 --- a/buildSrc/src/main/kotlin/yokai/build/ProjectExtensions.kt +++ b/buildSrc/src/main/kotlin/yokai/build/ProjectExtensions.kt @@ -1,6 +1,98 @@ package yokai.build +import com.android.build.api.dsl.CommonExtension +import org.gradle.accessors.dm.LibrariesForAndroidx +import org.gradle.accessors.dm.LibrariesForCompose +import org.gradle.accessors.dm.LibrariesForKotlinx +import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.api.Project +import org.gradle.api.tasks.testing.Test +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.provideDelegate +import org.gradle.kotlin.dsl.the +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradlePluginExtension +import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.File +val Project.androidx get() = the() +val Project.compose get() = the() +val Project.kotlinx get() = the() +val Project.libs get() = the() + val Project.generatedBuildDir: File get() = project.layout.buildDirectory.asFile.get().resolve("generated/yokai") + +internal fun Project.configureAndroid(commonExtension: CommonExtension<*, *, *, *, *, *>) { + commonExtension.apply { + compileSdk = AndroidConfig.COMPILE_SDK + defaultConfig { + minSdk = AndroidConfig.MIN_SDK + ndk { + version = AndroidConfig.NDK + } + } + compileOptions { + sourceCompatibility = AndroidConfig.JavaVersion + targetCompatibility = AndroidConfig.JavaVersion + isCoreLibraryDesugaringEnabled = true + } + } + tasks.withType().configureEach { + kotlinOptions { + jvmTarget = AndroidConfig.JavaVersion.toString() + // freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" + // freeCompilerArgs += "-Xcontext-receivers" + // Treat all Kotlin warnings as errors (disabled by default) + // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties + // val warningsAsErrors: String? by project + // allWarningsAsErrors = warningsAsErrors.toBoolean() + } + } + dependencies { + "coreLibraryDesugaring"(libs.desugar) + } +} + +internal fun Project.configureCompose(commonExtension: CommonExtension<*, *, *, *, *, *>) { + pluginManager.apply(kotlinx.plugins.compose.compiler.get().pluginId) + + commonExtension.apply { + buildFeatures { + compose = true + } + + dependencies { + "implementation"(platform(compose.bom)) + } + } + + extensions.configure { + featureFlags.set(setOf(ComposeFeatureFlag.OptimizeNonSkippingGroups)) + + val enableMetrics = project.providers.gradleProperty("enableComposeCompilerMetrics").orNull.toBoolean() + val enableReports = project.providers.gradleProperty("enableComposeCompilerReports").orNull.toBoolean() + + val rootBuildDir = rootProject.layout.buildDirectory.asFile.get() + val relativePath = projectDir.relativeTo(rootDir) + + if (enableMetrics) { + rootBuildDir.resolve("compose-metrics").resolve(relativePath).let(metricsDestination::set) + } + + if (enableReports) { + rootBuildDir.resolve("compose-reports").resolve(relativePath).let(reportsDestination::set) + } + } +} + +internal fun Project.configureTest() { + tasks.withType { + useJUnitPlatform() + testLogging { + events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) + } + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e169090b2b..c8fa08982e 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,8 +1,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - alias(androidx.plugins.library) - alias(kotlinx.plugins.multiplatform) + id("yokai.android.library") + kotlin("multiplatform") alias(kotlinx.plugins.serialization) } diff --git a/data/build.gradle.kts b/data/build.gradle.kts index e4d4542e3d..6f17a84c4b 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,7 +1,7 @@ plugins { - alias(kotlinx.plugins.multiplatform) + id("yokai.android.library") + kotlin("multiplatform") alias(kotlinx.plugins.serialization) - alias(androidx.plugins.library) alias(libs.plugins.sqldelight) } diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 6c31e4bfb9..e2f0e2da78 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -1,7 +1,7 @@ plugins { - alias(kotlinx.plugins.multiplatform) + id("yokai.android.library") + kotlin("multiplatform") alias(kotlinx.plugins.serialization) - alias(androidx.plugins.library) } kotlin { diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index c65c235f27..efae61e57b 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -4,6 +4,8 @@ agp = "8.7.3" lifecycle = "2.8.7" [libraries] +gradle = { module = "com.android.tools.build:gradle", version.ref = "agp" } + activity = { module = "androidx.activity:activity-ktx", version.ref = "activity" } activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity" } annotation = { module = "androidx.annotation:annotation", version = "1.9.1" } diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index 9fcc9e314c..afeeadc0fe 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -4,6 +4,9 @@ serialization = "1.7.3" xml_serialization = "0.90.3" [libraries] +gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +compose-compiler-gradle = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } + coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version = "1.10.1" } coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" } coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" } @@ -26,4 +29,4 @@ android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } -parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } +parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize" } diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts index 3879a5b92e..6cf146d185 100644 --- a/i18n/build.gradle.kts +++ b/i18n/build.gradle.kts @@ -2,8 +2,8 @@ import yokai.build.generatedBuildDir import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - alias(kotlinx.plugins.multiplatform) - alias(androidx.plugins.library) + id("yokai.android.library") + kotlin("multiplatform") alias(libs.plugins.moko) } diff --git a/presentation/core/build.gradle.kts b/presentation/core/build.gradle.kts index 2dd788bcb9..bb2b8d8303 100644 --- a/presentation/core/build.gradle.kts +++ b/presentation/core/build.gradle.kts @@ -1,6 +1,7 @@ plugins { - alias(androidx.plugins.library) - alias(kotlinx.plugins.android) + id("yokai.android.library") + id("yokai.android.library.compose") + kotlin("android") } android { @@ -12,6 +13,22 @@ android { } } +kotlin { + compilerOptions { + freeCompilerArgs.addAll( + "-opt-in=androidx.compose.animation.ExperimentalAnimationApi", + "-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi", + "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi", + "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", + "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", + "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", + "-opt-in=kotlinx.coroutines.FlowPreview", + ) + } +} + dependencies { api(libs.material) + + implementation(compose.bundles.compose) } diff --git a/presentation/widget/build.gradle.kts b/presentation/widget/build.gradle.kts index 4f4f4edb44..aa44817507 100644 --- a/presentation/widget/build.gradle.kts +++ b/presentation/widget/build.gradle.kts @@ -1,9 +1,9 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - alias(androidx.plugins.library) - alias(kotlinx.plugins.android) - alias(kotlinx.plugins.compose.compiler) + id("yokai.android.library") + id("yokai.android.library.compose") + kotlin("android") } android { diff --git a/source/api/build.gradle.kts b/source/api/build.gradle.kts index 8d6fe1986a..31f3f0856e 100644 --- a/source/api/build.gradle.kts +++ b/source/api/build.gradle.kts @@ -1,8 +1,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - alias(androidx.plugins.library) - alias(kotlinx.plugins.multiplatform) + id("yokai.android.library") + kotlin("multiplatform") alias(kotlinx.plugins.serialization) } From 55fad6722363b29aac26a57e057a072032c26ec9 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 25 Dec 2024 12:02:39 +0700 Subject: [PATCH 023/181] fix(EmptyScreen): Align center the message if it's not tablet UI --- app/src/main/java/yokai/presentation/component/EmptyScreen.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/yokai/presentation/component/EmptyScreen.kt b/app/src/main/java/yokai/presentation/component/EmptyScreen.kt index b68d9dc66d..06d49c6ee6 100644 --- a/app/src/main/java/yokai/presentation/component/EmptyScreen.kt +++ b/app/src/main/java/yokai/presentation/component/EmptyScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -154,6 +155,7 @@ private fun EmptyScreenImpl( text = message, color = MaterialTheme.colorScheme.textHint, style = MaterialTheme.typography.labelMedium, + textAlign = TextAlign.Center, ) actions() } From 448c93365a89201fded00aa282fae20277cbe847 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 25 Dec 2024 13:21:42 +0700 Subject: [PATCH 024/181] refactor: Try to mimic ExpandedAppBarLayout for Compose --- .../main/java/yokai/presentation/Scaffold.kt | 4 +- .../java/yokai/presentation/core/AppBar.kt | 625 ++++++++++++++++++ 2 files changed, 627 insertions(+), 2 deletions(-) create mode 100644 presentation/core/src/main/java/yokai/presentation/core/AppBar.kt diff --git a/app/src/main/java/yokai/presentation/Scaffold.kt b/app/src/main/java/yokai/presentation/Scaffold.kt index 4b29a2a373..3d588ccd54 100644 --- a/app/src/main/java/yokai/presentation/Scaffold.kt +++ b/app/src/main/java/yokai/presentation/Scaffold.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.RowScope import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -28,6 +27,7 @@ import androidx.core.view.WindowInsetsControllerCompat import dev.icerock.moko.resources.compose.stringResource import yokai.i18n.MR import yokai.presentation.component.ToolTipButton +import yokai.presentation.core.ExpandedAppBar @Composable fun YokaiScaffold( @@ -78,7 +78,7 @@ fun YokaiScaffold( scrollBehavior = scrollBehavior, actions = actions, ) - AppBarType.LARGE -> LargeTopAppBar( + AppBarType.LARGE -> ExpandedAppBar( title = { Text(text = title) }, diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt new file mode 100644 index 0000000000..d2306c12ef --- /dev/null +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -0,0 +1,625 @@ +package yokai.presentation.core + +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.AnimationState +import androidx.compose.animation.core.CubicBezierEasing +import androidx.compose.animation.core.DecayAnimationSpec +import androidx.compose.animation.core.FastOutLinearInEasing +import androidx.compose.animation.core.animateDecay +import androidx.compose.animation.core.animateTo +import androidx.compose.animation.rememberSplineBasedDecay +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.TopAppBarColors +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.TopAppBarState +import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.layout.AlignmentLine +import androidx.compose.ui.layout.LastBaseline +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.Velocity +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.isFinite +import androidx.compose.ui.unit.isSpecified +import androidx.compose.ui.util.fastFirst +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.roundToInt + +/** + * Composable replacement for [eu.kanade.tachiyomi.ui.base.ExpandedAppBarLayout] + * + * Copied from [androidx.compose.material3.LargeTopAppBar], modified to mimic J2K's + * [eu.kanade.tachiyomi.ui.base.ExpandedAppBarLayout] behaviors + */ +@Composable +fun ExpandedAppBar( + title: @Composable () -> Unit, + modifier: Modifier = Modifier, + navigationIcon: @Composable () -> Unit = {}, + actions: @Composable RowScope.() -> Unit = {}, + windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, + colors: TopAppBarColors = TopAppBarDefaults.largeTopAppBarColors(), + scrollBehavior: TopAppBarScrollBehavior? = null +) { + TwoRowsTopAppBar( + title = title, + titleTextStyle = MaterialTheme.typography.headlineMedium, + smallTitleTextStyle = MaterialTheme.typography.titleLarge, + titleBottomPadding = LargeTitleBottomPadding, + smallTitle = title, + modifier = modifier, + navigationIcon = navigationIcon, + actions = actions, + collapsedHeight = CollapsedContainerHeight, + expandedHeight = ExpandedContainerHeight, + windowInsets = windowInsets, + colors = colors, + scrollBehavior = scrollBehavior, + ) +} + +@Composable +private fun TwoRowsTopAppBar( + modifier: Modifier = Modifier, + title: @Composable () -> Unit, + titleTextStyle: TextStyle, + titleBottomPadding: Dp, + smallTitle: @Composable () -> Unit, + smallTitleTextStyle: TextStyle, + navigationIcon: @Composable () -> Unit, + actions: @Composable RowScope.() -> Unit, + collapsedHeight: Dp, + expandedHeight: Dp, + windowInsets: WindowInsets, + colors: TopAppBarColors, + scrollBehavior: TopAppBarScrollBehavior? +) { + require(collapsedHeight.isSpecified && collapsedHeight.isFinite) { + "The collapsedHeight is expected to be specified and finite" + } + require(expandedHeight.isSpecified && expandedHeight.isFinite) { + "The expandedHeight is expected to be specified and finite" + } + require(expandedHeight >= collapsedHeight) { + "The expandedHeight is expected to be greater or equal to the collapsedHeight" + } + val expandedHeightPx: Float + val collapsedHeightPx: Float + val titleBottomPaddingPx: Int + LocalDensity.current.run { + expandedHeightPx = expandedHeight.toPx() + collapsedHeightPx = collapsedHeight.toPx() + titleBottomPaddingPx = titleBottomPadding.roundToPx() + } + + // Sets the app bar's height offset limit to hide just the bottom title area and keep top title + // visible when collapsed. + SideEffect { + if (scrollBehavior?.state?.heightOffsetLimit != -expandedHeightPx) { + scrollBehavior?.state?.heightOffsetLimit = -expandedHeightPx + } + } + + // Obtain the container Color from the TopAppBarColors using the `collapsedFraction`, as the + // bottom part of this TwoRowsTopAppBar changes color at the same rate the app bar expands or + // collapse. + // This will potentially animate or interpolate a transition between the container color and the + // container's scrolled color according to the app bar's scroll state. + val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f + val topColorTransitionFraction = scrollBehavior?.state?.topCollapsedFraction(collapsedHeightPx) ?: 0f + val bottomColorTransitionFraction = scrollBehavior?.state?.bottomCollapsedFraction(collapsedHeightPx, expandedHeightPx) ?: 0f + + val appBarContainerColor = + lerp( + colors.containerColor, + colors.scrolledContainerColor, + FastOutLinearInEasing.transform(colorTransitionFraction) + ) + + // Wrap the given actions in a Row. + val actionsRow = + @Composable { + Row( + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + content = actions + ) + } + val topTitleAlpha = TopTitleAlphaEasing.transform(topColorTransitionFraction) + val bottomTitleAlpha = 1f - bottomColorTransitionFraction + // Hide the top row title semantics when its alpha value goes below 0.5 threshold. + // Hide the bottom row title semantics when the top title semantics are active. + val hideTopRowSemantics = topColorTransitionFraction < 0.5f + val hideBottomRowSemantics = bottomColorTransitionFraction < 0.5f + + // Set up support for resizing the top app bar when vertically dragging the bar itself. + val appBarDragModifier = + if (scrollBehavior != null && !scrollBehavior.isPinned) { + Modifier.draggable( + orientation = Orientation.Vertical, + state = + rememberDraggableState { delta -> scrollBehavior.state.heightOffset += delta }, + onDragStopped = { velocity -> + settleAppBar( + scrollBehavior.state, + velocity, + scrollBehavior.flingAnimationSpec, + scrollBehavior.snapAnimationSpec + ) + } + ) + } else { + Modifier + } + + Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) { + Column { + AppBarLayout( + modifier = + Modifier + .windowInsetsPadding(windowInsets) + // clip after padding so we don't show the title over the inset area + .clipToBounds() + .heightIn(max = collapsedHeight), + scrolledOffset = { + scrollBehavior?.state?.topHeightOffset( + topHeightPx = collapsedHeightPx, + totalHeightPx = expandedHeightPx, + ) ?: 0f + }, + navigationIconContentColor = colors.navigationIconContentColor, + titleContentColor = colors.titleContentColor, + actionIconContentColor = colors.actionIconContentColor, + title = smallTitle, + titleTextStyle = smallTitleTextStyle, + titleAlpha = topTitleAlpha, + titleVerticalArrangement = Arrangement.Bottom, + titleHorizontalArrangement = Arrangement.Start, + titleBottomPadding = 0, + hideTitleSemantics = hideTopRowSemantics, + navigationIcon = navigationIcon, + actions = actionsRow, + ) + AppBarLayout( + modifier = + Modifier + // only apply the horizontal sides of the window insets padding, since the + // top + // padding will always be applied by the layout above + .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal)) + .clipToBounds() + .heightIn(max = expandedHeight - collapsedHeight), + scrolledOffset = { + scrollBehavior?.state?.bottomHeightOffset( + topHeightPx = collapsedHeightPx, + totalHeightPx = expandedHeightPx, + ) ?: 0f + }, + navigationIconContentColor = colors.navigationIconContentColor, + titleContentColor = colors.titleContentColor, + actionIconContentColor = colors.actionIconContentColor, + title = title, + titleTextStyle = titleTextStyle, + titleAlpha = bottomTitleAlpha, + titleVerticalArrangement = Arrangement.Bottom, + titleHorizontalArrangement = Arrangement.Start, + titleBottomPadding = titleBottomPaddingPx, + hideTitleSemantics = hideBottomRowSemantics, + navigationIcon = {}, + actions = {} + ) + } + } +} + +@Composable +private fun AppBarLayout( + modifier: Modifier, + scrolledOffset: ScrolledOffset, + navigationIconContentColor: Color, + titleContentColor: Color, + actionIconContentColor: Color, + title: @Composable () -> Unit, + titleTextStyle: TextStyle, + titleAlpha: Float, + titleVerticalArrangement: Arrangement.Vertical, + titleHorizontalArrangement: Arrangement.Horizontal, + titleBottomPadding: Int, + hideTitleSemantics: Boolean, + navigationIcon: @Composable () -> Unit, + actions: @Composable () -> Unit, +) { + Layout( + { + Box(Modifier + .layoutId("navigationIcon") + .padding(start = TopAppBarHorizontalPadding)) { + CompositionLocalProvider( + LocalContentColor provides navigationIconContentColor, + content = navigationIcon + ) + } + Box( + Modifier + .layoutId("title") + .padding(horizontal = TopAppBarHorizontalPadding) + .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics {} else Modifier) + .graphicsLayer(alpha = titleAlpha) + ) { + ProvideContentColorTextStyle( + contentColor = titleContentColor, + textStyle = titleTextStyle, + content = title + ) + } + Box(Modifier + .layoutId("actionIcons") + .padding(end = TopAppBarHorizontalPadding)) { + CompositionLocalProvider( + LocalContentColor provides actionIconContentColor, + content = actions + ) + } + }, + modifier = modifier, + ) { measurables, constraints -> + val navigationIconPlaceable = + measurables + .fastFirst { it.layoutId == "navigationIcon" } + .measure(constraints.copy(minWidth = 0)) + val actionIconsPlaceable = + measurables + .fastFirst { it.layoutId == "actionIcons" } + .measure(constraints.copy(minWidth = 0)) + + val maxTitleWidth = + if (constraints.maxWidth == Constraints.Infinity) { + constraints.maxWidth + } else { + (constraints.maxWidth - navigationIconPlaceable.width - actionIconsPlaceable.width) + .coerceAtLeast(0) + } + val titlePlaceable = + measurables + .fastFirst { it.layoutId == "title" } + .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth)) + + // Locate the title's baseline. + val titleBaseline = + if (titlePlaceable[LastBaseline] != AlignmentLine.Unspecified) { + titlePlaceable[LastBaseline] + } else { + 0 + } + + // Subtract the scrolledOffset from the maxHeight. The scrolledOffset is expected to be + // equal or smaller than zero. + val scrolledOffsetValue = scrolledOffset.offset() + val heightOffset = if (scrolledOffsetValue.isNaN()) 0 else scrolledOffsetValue.roundToInt() + + val layoutHeight = + if (constraints.maxHeight == Constraints.Infinity) { + constraints.maxHeight + } else { + constraints.maxHeight + heightOffset + } + + layout(constraints.maxWidth, layoutHeight) { + // Navigation icon + navigationIconPlaceable.placeRelative( + x = 0, + y = + when (titleVerticalArrangement) { + Arrangement.Bottom -> { + val padding = (constraints.maxHeight - navigationIconPlaceable.height) / 2 + val paddingFromBottom = padding - (navigationIconPlaceable.height - titleBaseline) + val heightWithPadding = paddingFromBottom + navigationIconPlaceable.height + val adjustedBottomPadding = + if (heightWithPadding > constraints.maxHeight) { + paddingFromBottom - + (heightWithPadding - constraints.maxHeight) + } else { + paddingFromBottom + } + + layoutHeight - navigationIconPlaceable.height - max(0, adjustedBottomPadding) + } + else -> (layoutHeight - navigationIconPlaceable.height) / 2 + } + ) + + // Title + titlePlaceable.placeRelative( + x = + when (titleHorizontalArrangement) { + Arrangement.Center -> { + var baseX = (constraints.maxWidth - titlePlaceable.width) / 2 + if (baseX < navigationIconPlaceable.width) { + // May happen if the navigation is wider than the actions and the + // title is long. In this case, prioritize showing more of the title + // by + // offsetting it to the right. + baseX += (navigationIconPlaceable.width - baseX) + } else if ( + baseX + titlePlaceable.width > + constraints.maxWidth - actionIconsPlaceable.width + ) { + // May happen if the actions are wider than the navigation and the + // title + // is long. In this case, offset to the left. + baseX += + ((constraints.maxWidth - actionIconsPlaceable.width) - + (baseX + titlePlaceable.width)) + } + baseX + } + Arrangement.End -> + constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width + // Arrangement.Start. + // An TopAppBarTitleInset will make sure the title is offset in case the + // navigation icon is missing. + else -> max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width) + }, + y = + when (titleVerticalArrangement) { + Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2 + // Apply bottom padding from the title's baseline only when the Arrangement + // is + // "Bottom". + Arrangement.Bottom -> { + val padding = if (titleBottomPadding == 0) { + (constraints.maxHeight - titlePlaceable.height) / 2 + } else { + titleBottomPadding + } + // Calculate the actual padding from the bottom of the title, taking + // into account its baseline. + val paddingFromBottom = + padding - (titlePlaceable.height - titleBaseline) + // Adjust the bottom padding to a smaller number if there is no room + // to + // fit the title. + val heightWithPadding = paddingFromBottom + titlePlaceable.height + val adjustedBottomPadding = + if (heightWithPadding > constraints.maxHeight) { + paddingFromBottom - + (heightWithPadding - constraints.maxHeight) + } else { + paddingFromBottom + } + + layoutHeight - titlePlaceable.height - max(0, adjustedBottomPadding) + } + // Arrangement.Top + else -> 0 + } + ) + + // Action icons + actionIconsPlaceable.placeRelative( + x = constraints.maxWidth - actionIconsPlaceable.width, + y = (layoutHeight - actionIconsPlaceable.height) / 2 + ) + } + } +} + +@Composable +internal fun ProvideContentColorTextStyle( + contentColor: Color, + textStyle: TextStyle, + content: @Composable () -> Unit +) { + val mergedStyle = LocalTextStyle.current.merge(textStyle) + CompositionLocalProvider( + LocalContentColor provides contentColor, + LocalTextStyle provides mergedStyle, + content = content + ) +} + +private fun interface ScrolledOffset { + fun offset(): Float +} + +private suspend fun settleAppBar( + state: TopAppBarState, + velocity: Float, + flingAnimationSpec: DecayAnimationSpec?, + snapAnimationSpec: AnimationSpec? +): Velocity { + // Check if the app bar is completely collapsed/expanded. If so, no need to settle the app bar, + // and just return Zero Velocity. + // Note that we don't check for 0f due to float precision with the collapsedFraction + // calculation. + if (state.collapsedFraction < 0.01f || state.collapsedFraction == 1f) { + return Velocity.Zero + } + var remainingVelocity = velocity + // In case there is an initial velocity that was left after a previous user fling, animate to + // continue the motion to expand or collapse the app bar. + if (flingAnimationSpec != null && abs(velocity) > 1f) { + var lastValue = 0f + AnimationState( + initialValue = 0f, + initialVelocity = velocity, + ) + .animateDecay(flingAnimationSpec) { + val delta = value - lastValue + val initialHeightOffset = state.heightOffset + state.heightOffset = initialHeightOffset + delta + val consumed = abs(initialHeightOffset - state.heightOffset) + lastValue = value + remainingVelocity = this.velocity + // avoid rounding errors and stop if anything is unconsumed + if (abs(delta - consumed) > 0.5f) this.cancelAnimation() + } + } + // Snap if animation specs were provided. + if (snapAnimationSpec != null) { + // FIXME: Only snap the top app bar + if (state.heightOffset < 0 && state.heightOffset > state.heightOffsetLimit) { + AnimationState(initialValue = state.heightOffset).animateTo( + if (state.collapsedFraction < 0.5f) { + 0f + } else { + state.heightOffsetLimit + }, + animationSpec = snapAnimationSpec + ) { + state.heightOffset = value + } + } + } + + return Velocity(0f, remainingVelocity) +} + +private fun TopAppBarState.topHeightOffset(topHeightPx: Float, totalHeightPx: Float): Float { + val offset = heightOffset + (totalHeightPx - topHeightPx) + return offset.coerceIn(-topHeightPx, 0f) +} + +private fun TopAppBarState.bottomHeightOffset(topHeightPx: Float, totalHeightPx: Float): Float { + return heightOffset.coerceIn(topHeightPx - totalHeightPx, 0f) +} + +private fun TopAppBarState.topCollapsedFraction(topHeightPx: Float): Float { + return heightOffset / -topHeightPx +} + +private fun TopAppBarState.bottomCollapsedFraction(topHeightPx: Float, totalHeightPx: Float): Float { + return heightOffset / (topHeightPx - totalHeightPx) +} + +@Composable +fun enterAlwaysCollapsedScrollBehavior( + state: TopAppBarState = rememberTopAppBarState(), + canScroll: () -> Boolean = { true }, + flingAnimationSpec: DecayAnimationSpec? = rememberSplineBasedDecay() +): TopAppBarScrollBehavior { + val topHeightPx: Float + val totalHeightPx: Float + LocalDensity.current.run { + topHeightPx = CollapsedContainerHeight.toPx() + totalHeightPx = ExpandedContainerHeight.toPx() + } + + return EnterAlwaysCollapsedScrollBehavior( + state = state, + flingAnimationSpec = flingAnimationSpec, + canScroll = canScroll, + topHeightPx = topHeightPx, + totalHeightPx = totalHeightPx, + ) +} + +// TODO: Current it behaves exactly like EnterAlways +private class EnterAlwaysCollapsedScrollBehavior( + override val state: TopAppBarState, + override val flingAnimationSpec: DecayAnimationSpec?, + val canScroll: () -> Boolean = { true }, + val topHeightPx: Float, + val totalHeightPx: Float, +) : TopAppBarScrollBehavior { + override val snapAnimationSpec: AnimationSpec? = null // J2K's app bar doesn't do snap + override val isPinned: Boolean = false + override var nestedScrollConnection = + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + // Don't intercept if scrolling down. + if (!canScroll()) return Offset.Zero + + val prevHeightOffset = state.heightOffset + state.heightOffset += available.y + return if (prevHeightOffset != state.heightOffset) { + // We're in the middle of top app bar collapse or expand. + // Consume only the scroll on the Y axis. + available.copy(x = 0f) + } else { + Offset.Zero + } + } + + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource + ): Offset { + if (!canScroll()) return Offset.Zero + state.contentOffset += consumed.y + + if (available.y < 0f || consumed.y < 0f) { + // When scrolling up, just update the state's height offset. + val oldHeightOffset = state.heightOffset + state.heightOffset += consumed.y + return Offset(0f, state.heightOffset - oldHeightOffset) + } + + if (consumed.y == 0f && available.y > 0) { + // Reset the total content offset to zero when scrolling all the way down. This + // will eliminate some float precision inaccuracies. + state.contentOffset = 0f + } + + if (available.y > 0f) { + // Adjust the height offset in case the consumed delta Y is less than what was + // recorded as available delta Y in the pre-scroll. + val oldHeightOffset = state.heightOffset + state.heightOffset += available.y + return Offset(0f, state.heightOffset - oldHeightOffset) + } + return Offset.Zero + } + + override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + val superConsumed = super.onPostFling(consumed, available) + return superConsumed + + settleAppBar(state, available.y, flingAnimationSpec, snapAnimationSpec) + } + } +} + +val CollapsedContainerHeight = 64.0.dp +val ExpandedContainerHeight = 152.0.dp +internal val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f) +private val MediumTitleBottomPadding = 24.dp +private val LargeTitleBottomPadding = 28.dp +private val TopAppBarHorizontalPadding = 4.dp +private val TopAppBarTitleInset = 16.dp - TopAppBarHorizontalPadding From dec1a700915ba48a68b30324a8f5df2e7c3cae8f Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 25 Dec 2024 16:12:11 +0700 Subject: [PATCH 025/181] fix(AppBar): Re-introduce snap but only do it to the top bar --- CHANGELOG.md | 3 +- .../java/yokai/presentation/core/AppBar.kt | 53 +++++++++++-------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e7a83e792..f1dacc1ab3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,8 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Refactor Library to utilize Flow even more - Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.1 - Refactor EmptyView to use Compose -- Refactor Reader ChapterTransition to use Compose +- Refactor Reader ChapterTransition to use Compose (@arkon) +- [Experimental] Add modified version of LargeTopAppBar that mimic J2K's ExpandedAppBarLayout ## [1.9.7] diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt index d2306c12ef..c1d5580c48 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -5,8 +5,10 @@ import androidx.compose.animation.core.AnimationState import androidx.compose.animation.core.CubicBezierEasing import androidx.compose.animation.core.DecayAnimationSpec import androidx.compose.animation.core.FastOutLinearInEasing +import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateDecay import androidx.compose.animation.core.animateTo +import androidx.compose.animation.core.spring import androidx.compose.animation.rememberSplineBasedDecay import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable @@ -141,9 +143,7 @@ private fun TwoRowsTopAppBar( // collapse. // This will potentially animate or interpolate a transition between the container color and the // container's scrolled color according to the app bar's scroll state. - val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f - val topColorTransitionFraction = scrollBehavior?.state?.topCollapsedFraction(collapsedHeightPx) ?: 0f - val bottomColorTransitionFraction = scrollBehavior?.state?.bottomCollapsedFraction(collapsedHeightPx, expandedHeightPx) ?: 0f + val colorTransitionFraction = scrollBehavior?.state?.bottomCollapsedFraction(collapsedHeightPx, expandedHeightPx) ?: 0f val appBarContainerColor = lerp( @@ -161,12 +161,12 @@ private fun TwoRowsTopAppBar( content = actions ) } - val topTitleAlpha = TopTitleAlphaEasing.transform(topColorTransitionFraction) - val bottomTitleAlpha = 1f - bottomColorTransitionFraction + val topTitleAlpha = TitleAlphaEasing.transform(colorTransitionFraction) + val bottomTitleAlpha = 1f - colorTransitionFraction // Hide the top row title semantics when its alpha value goes below 0.5 threshold. // Hide the bottom row title semantics when the top title semantics are active. - val hideTopRowSemantics = topColorTransitionFraction < 0.5f - val hideBottomRowSemantics = bottomColorTransitionFraction < 0.5f + val hideTopRowSemantics = colorTransitionFraction < 0.5f + val hideBottomRowSemantics = !hideTopRowSemantics // Set up support for resizing the top app bar when vertically dragging the bar itself. val appBarDragModifier = @@ -179,6 +179,8 @@ private fun TwoRowsTopAppBar( settleAppBar( scrollBehavior.state, velocity, + collapsedHeightPx, + expandedHeightPx, scrollBehavior.flingAnimationSpec, scrollBehavior.snapAnimationSpec ) @@ -461,14 +463,16 @@ private fun interface ScrolledOffset { private suspend fun settleAppBar( state: TopAppBarState, velocity: Float, + topHeightPx: Float, + totalHeightPx: Float, flingAnimationSpec: DecayAnimationSpec?, - snapAnimationSpec: AnimationSpec? + snapAnimationSpec: AnimationSpec?, ): Velocity { // Check if the app bar is completely collapsed/expanded. If so, no need to settle the app bar, // and just return Zero Velocity. // Note that we don't check for 0f due to float precision with the collapsedFraction // calculation. - if (state.collapsedFraction < 0.01f || state.collapsedFraction == 1f) { + if (state.topCollapsedFraction(topHeightPx, totalHeightPx) < 0.01f || state.topCollapsedFraction(topHeightPx, totalHeightPx) == 1f) { return Velocity.Zero } var remainingVelocity = velocity @@ -493,17 +497,16 @@ private suspend fun settleAppBar( } // Snap if animation specs were provided. if (snapAnimationSpec != null) { - // FIXME: Only snap the top app bar - if (state.heightOffset < 0 && state.heightOffset > state.heightOffsetLimit) { - AnimationState(initialValue = state.heightOffset).animateTo( - if (state.collapsedFraction < 0.5f) { + if (state.topHeightOffset(topHeightPx, totalHeightPx) < 0 && state.topHeightOffset(topHeightPx, totalHeightPx) > -topHeightPx) { + AnimationState(initialValue = state.topHeightOffset(topHeightPx, totalHeightPx)).animateTo( + if (state.topCollapsedFraction(topHeightPx, totalHeightPx) < 0.5f) { 0f } else { - state.heightOffsetLimit + -topHeightPx }, animationSpec = snapAnimationSpec ) { - state.heightOffset = value + state.heightOffset = value + (topHeightPx - totalHeightPx) } } } @@ -520,18 +523,21 @@ private fun TopAppBarState.bottomHeightOffset(topHeightPx: Float, totalHeightPx: return heightOffset.coerceIn(topHeightPx - totalHeightPx, 0f) } -private fun TopAppBarState.topCollapsedFraction(topHeightPx: Float): Float { - return heightOffset / -topHeightPx +private fun TopAppBarState.topCollapsedFraction(topHeightPx: Float, totalHeightPx: Float): Float { + val offset = topHeightOffset(topHeightPx, totalHeightPx) + return offset / -topHeightPx } private fun TopAppBarState.bottomCollapsedFraction(topHeightPx: Float, totalHeightPx: Float): Float { - return heightOffset / (topHeightPx - totalHeightPx) + val offset = bottomHeightOffset(topHeightPx, totalHeightPx) + return offset / (topHeightPx - totalHeightPx) } @Composable fun enterAlwaysCollapsedScrollBehavior( state: TopAppBarState = rememberTopAppBarState(), canScroll: () -> Boolean = { true }, + snapAnimationSpec: AnimationSpec? = spring(stiffness = Spring.StiffnessMediumLow), flingAnimationSpec: DecayAnimationSpec? = rememberSplineBasedDecay() ): TopAppBarScrollBehavior { val topHeightPx: Float @@ -543,6 +549,7 @@ fun enterAlwaysCollapsedScrollBehavior( return EnterAlwaysCollapsedScrollBehavior( state = state, + snapAnimationSpec = snapAnimationSpec, flingAnimationSpec = flingAnimationSpec, canScroll = canScroll, topHeightPx = topHeightPx, @@ -550,21 +557,21 @@ fun enterAlwaysCollapsedScrollBehavior( ) } -// TODO: Current it behaves exactly like EnterAlways private class EnterAlwaysCollapsedScrollBehavior( override val state: TopAppBarState, + override val snapAnimationSpec: AnimationSpec?, override val flingAnimationSpec: DecayAnimationSpec?, val canScroll: () -> Boolean = { true }, val topHeightPx: Float, val totalHeightPx: Float, ) : TopAppBarScrollBehavior { - override val snapAnimationSpec: AnimationSpec? = null // J2K's app bar doesn't do snap override val isPinned: Boolean = false override var nestedScrollConnection = object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { // Don't intercept if scrolling down. - if (!canScroll()) return Offset.Zero + if (!canScroll() || (available.y > 0f && state.topHeightOffset(topHeightPx, totalHeightPx) >= 0f)) + return Offset.Zero val prevHeightOffset = state.heightOffset state.heightOffset += available.y @@ -611,14 +618,14 @@ private class EnterAlwaysCollapsedScrollBehavior( override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { val superConsumed = super.onPostFling(consumed, available) return superConsumed + - settleAppBar(state, available.y, flingAnimationSpec, snapAnimationSpec) + settleAppBar(state, available.y, topHeightPx, totalHeightPx, flingAnimationSpec, snapAnimationSpec) } } } val CollapsedContainerHeight = 64.0.dp val ExpandedContainerHeight = 152.0.dp -internal val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f) +internal val TitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f) private val MediumTitleBottomPadding = 24.dp private val LargeTitleBottomPadding = 28.dp private val TopAppBarHorizontalPadding = 4.dp From 120d2cfb963da025f51077fe32394a7b79e3ba07 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 25 Dec 2024 16:19:59 +0700 Subject: [PATCH 026/181] feat(AppBar): EnterAlwaysCollapsed for Compose Google left out EnterAlwaysCollapsed for some reason --- .../settings/SettingsCommonWidget.kt | 6 ++-- .../java/yokai/presentation/core/AppBar.kt | 28 ++----------------- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt b/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt index 3d6cd91655..728948f4be 100644 --- a/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt +++ b/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -20,6 +19,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.util.compose.LocalAlertDialog import eu.kanade.tachiyomi.util.compose.LocalBackPress import eu.kanade.tachiyomi.util.compose.currentOrThrow +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.delay import uy.kohesive.injekt.injectLazy import yokai.presentation.AppBarType @@ -28,7 +28,7 @@ import yokai.presentation.component.Gap import yokai.presentation.component.preference.Preference import yokai.presentation.component.preference.PreferenceItem import yokai.presentation.component.preference.widget.PreferenceGroupHeader -import kotlin.time.Duration.Companion.seconds +import yokai.presentation.core.enterAlwaysCollapsedScrollBehavior @Composable fun SettingsScaffold( @@ -48,7 +48,7 @@ fun SettingsScaffold( title = title, appBarType = appBarType ?: if (useLargeAppBar) AppBarType.LARGE else AppBarType.SMALL, actions = appBarActions, - scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior( + scrollBehavior = enterAlwaysCollapsedScrollBehavior( state = rememberTopAppBarState(), canScroll = { listState.canScrollForward || listState.canScrollBackward }, ), diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt index c1d5580c48..429b68a77b 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -10,9 +10,6 @@ import androidx.compose.animation.core.animateDecay import androidx.compose.animation.core.animateTo import androidx.compose.animation.core.spring import androidx.compose.animation.rememberSplineBasedDecay -import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.draggable -import androidx.compose.foundation.gestures.rememberDraggableState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -168,29 +165,7 @@ private fun TwoRowsTopAppBar( val hideTopRowSemantics = colorTransitionFraction < 0.5f val hideBottomRowSemantics = !hideTopRowSemantics - // Set up support for resizing the top app bar when vertically dragging the bar itself. - val appBarDragModifier = - if (scrollBehavior != null && !scrollBehavior.isPinned) { - Modifier.draggable( - orientation = Orientation.Vertical, - state = - rememberDraggableState { delta -> scrollBehavior.state.heightOffset += delta }, - onDragStopped = { velocity -> - settleAppBar( - scrollBehavior.state, - velocity, - collapsedHeightPx, - expandedHeightPx, - scrollBehavior.flingAnimationSpec, - scrollBehavior.snapAnimationSpec - ) - } - ) - } else { - Modifier - } - - Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) { + Surface(modifier = modifier, color = appBarContainerColor) { Column { AppBarLayout( modifier = @@ -557,6 +532,7 @@ fun enterAlwaysCollapsedScrollBehavior( ) } +// FIXME: AppBar size is overflowing if user flick the screen too fast private class EnterAlwaysCollapsedScrollBehavior( override val state: TopAppBarState, override val snapAnimationSpec: AnimationSpec?, From f78d4e9e6a6c8a6ba59a582e0f1466d13a76f1bb Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 25 Dec 2024 21:06:38 +0700 Subject: [PATCH 027/181] fix(AppBar): Sizing issue when user flick too hard --- .../settings/SettingsCommonWidget.kt | 1 + .../java/yokai/presentation/core/AppBar.kt | 37 +++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt b/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt index 728948f4be..cff9066e2a 100644 --- a/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt +++ b/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt @@ -51,6 +51,7 @@ fun SettingsScaffold( scrollBehavior = enterAlwaysCollapsedScrollBehavior( state = rememberTopAppBarState(), canScroll = { listState.canScrollForward || listState.canScrollBackward }, + isAtTop = { listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 }, ), ) { innerPadding -> alertDialog.content?.let { it() } diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt index 429b68a77b..1d4974948d 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -489,9 +489,21 @@ private suspend fun settleAppBar( return Velocity(0f, remainingVelocity) } +/** + * Default values: + * - Top app bar height: 128px + * - Total app bar height: 304px + * - Bottom app bar height: 176px + * - Top offset limit: (-(Total), (Top - Total)) = (-304px, -176px) + * - Bottom offset limit: ((Top - Total), 0) = (-176px, 0px) + */ + +private fun TopAppBarState.rawTopHeightOffset(topHeightPx: Float, totalHeightPx: Float): Float { + return heightOffset + (totalHeightPx - topHeightPx) +} + private fun TopAppBarState.topHeightOffset(topHeightPx: Float, totalHeightPx: Float): Float { - val offset = heightOffset + (totalHeightPx - topHeightPx) - return offset.coerceIn(-topHeightPx, 0f) + return rawTopHeightOffset(topHeightPx, totalHeightPx).coerceIn(-topHeightPx, 0f) } private fun TopAppBarState.bottomHeightOffset(topHeightPx: Float, totalHeightPx: Float): Float { @@ -512,6 +524,7 @@ private fun TopAppBarState.bottomCollapsedFraction(topHeightPx: Float, totalHeig fun enterAlwaysCollapsedScrollBehavior( state: TopAppBarState = rememberTopAppBarState(), canScroll: () -> Boolean = { true }, + isAtTop: () -> Boolean = { true }, snapAnimationSpec: AnimationSpec? = spring(stiffness = Spring.StiffnessMediumLow), flingAnimationSpec: DecayAnimationSpec? = rememberSplineBasedDecay() ): TopAppBarScrollBehavior { @@ -527,30 +540,40 @@ fun enterAlwaysCollapsedScrollBehavior( snapAnimationSpec = snapAnimationSpec, flingAnimationSpec = flingAnimationSpec, canScroll = canScroll, + isAtTop = isAtTop, topHeightPx = topHeightPx, totalHeightPx = totalHeightPx, ) } -// FIXME: AppBar size is overflowing if user flick the screen too fast private class EnterAlwaysCollapsedScrollBehavior( override val state: TopAppBarState, override val snapAnimationSpec: AnimationSpec?, override val flingAnimationSpec: DecayAnimationSpec?, val canScroll: () -> Boolean = { true }, + // FIXME: See if it's possible to eliminate this argument + val isAtTop: () -> Boolean = { true }, val topHeightPx: Float, val totalHeightPx: Float, ) : TopAppBarScrollBehavior { override val isPinned: Boolean = false override var nestedScrollConnection = object : NestedScrollConnection { + private fun TopAppBarState.setClampedOffsetIfAtTop(offset: Float) { + heightOffset = if (isAtTop()) { + offset + } else { + offset.coerceIn(-totalHeightPx, (topHeightPx - totalHeightPx)) + } + } + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { // Don't intercept if scrolling down. - if (!canScroll() || (available.y > 0f && state.topHeightOffset(topHeightPx, totalHeightPx) >= 0f)) + if (!canScroll() || (available.y > 0f && state.rawTopHeightOffset(topHeightPx, totalHeightPx) >= 0f)) return Offset.Zero val prevHeightOffset = state.heightOffset - state.heightOffset += available.y + state.setClampedOffsetIfAtTop(state.heightOffset + available.y) return if (prevHeightOffset != state.heightOffset) { // We're in the middle of top app bar collapse or expand. // Consume only the scroll on the Y axis. @@ -571,7 +594,7 @@ private class EnterAlwaysCollapsedScrollBehavior( if (available.y < 0f || consumed.y < 0f) { // When scrolling up, just update the state's height offset. val oldHeightOffset = state.heightOffset - state.heightOffset += consumed.y + state.setClampedOffsetIfAtTop(state.heightOffset + consumed.y) return Offset(0f, state.heightOffset - oldHeightOffset) } @@ -585,7 +608,7 @@ private class EnterAlwaysCollapsedScrollBehavior( // Adjust the height offset in case the consumed delta Y is less than what was // recorded as available delta Y in the pre-scroll. val oldHeightOffset = state.heightOffset - state.heightOffset += available.y + state.setClampedOffsetIfAtTop(state.heightOffset + available.y) return Offset(0f, state.heightOffset - oldHeightOffset) } return Offset.Zero From 23d4fb1fdde587eed8aa04e7ed365d0d3800e880 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 26 Dec 2024 07:56:15 +0700 Subject: [PATCH 028/181] fix(AppBar): Actions aren't aligned properly --- .../java/yokai/presentation/core/AppBar.kt | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt index 1d4974948d..0d6cb21141 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -338,7 +338,7 @@ private fun AppBarLayout( layoutHeight - navigationIconPlaceable.height - max(0, adjustedBottomPadding) } else -> (layoutHeight - navigationIconPlaceable.height) / 2 - } + }, ) // Title @@ -405,13 +405,30 @@ private fun AppBarLayout( } // Arrangement.Top else -> 0 - } + }, ) // Action icons actionIconsPlaceable.placeRelative( x = constraints.maxWidth - actionIconsPlaceable.width, - y = (layoutHeight - actionIconsPlaceable.height) / 2 + y = + when (titleVerticalArrangement) { + Arrangement.Bottom -> { + val padding = (constraints.maxHeight - actionIconsPlaceable.height) / 2 + val paddingFromBottom = padding - (actionIconsPlaceable.height - titleBaseline) + val heightWithPadding = paddingFromBottom + actionIconsPlaceable.height + val adjustedBottomPadding = + if (heightWithPadding > constraints.maxHeight) { + paddingFromBottom - + (heightWithPadding - constraints.maxHeight) + } else { + paddingFromBottom + } + + layoutHeight - actionIconsPlaceable.height - max(0, adjustedBottomPadding) + } + else -> (layoutHeight - actionIconsPlaceable.height) / 2 + }, ) } } From 96348dbf7d151796d860907dda901cc24d42e87f Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 26 Dec 2024 08:06:37 +0700 Subject: [PATCH 029/181] fix(browse): Disable stable id This reverts commit 8ac818797777b626fbe2fd81cde9f0be3b1cce88. --- .../kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt index 89989372e0..28b847bfca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt @@ -177,7 +177,7 @@ open class BrowseSourceController(bundle: Bundle) : super.onViewCreated(view) // Initialize adapter, scroll listener and recycler views - adapter = FlexibleAdapter(null, this, true) + adapter = FlexibleAdapter(null, this) setupRecycler(view) binding.fab.isVisible = presenter.sourceFilters.isNotEmpty() From 1cc8abb59929c191cbbd6e84343e8198d8a1bfda Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 26 Dec 2024 08:23:59 +0700 Subject: [PATCH 030/181] chore: Adjust aspect ratio to 2:3 For easier migration to Compose --- app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt | 2 +- .../eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index 956bca1be7..50f63727e2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -92,7 +92,7 @@ class LibraryItem( binding.coverThumbnail.adjustViewBounds = false binding.coverThumbnail.updateLayoutParams { height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT - dimensionRatio = "15:22" + dimensionRatio = "2:3" } } if (libraryLayout != LAYOUT_COMFORTABLE_GRID) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt index 20b4a8ebe0..d5e4818672 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceItem.kt @@ -66,7 +66,7 @@ class BrowseSourceItem( binding.coverThumbnail.adjustViewBounds = false binding.coverThumbnail.updateLayoutParams { height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT - dimensionRatio = "15:22" + dimensionRatio = "2:3" } } BrowseSourceGridHolder(view, adapter, listType == LibraryItem.LAYOUT_COMPACT_GRID, outlineOnCovers.get()) From 60ef9482c86bf63486a09692519ee4f4084f244b Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 26 Dec 2024 13:55:43 +0700 Subject: [PATCH 031/181] style(AppBar): Scrolled container color --- app/src/main/java/yokai/presentation/Scaffold.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/yokai/presentation/Scaffold.kt b/app/src/main/java/yokai/presentation/Scaffold.kt index 3d588ccd54..6d411f5a91 100644 --- a/app/src/main/java/yokai/presentation/Scaffold.kt +++ b/app/src/main/java/yokai/presentation/Scaffold.kt @@ -44,7 +44,7 @@ fun YokaiScaffold( ) { val view = LocalView.current val useDarkIcons = MaterialTheme.colorScheme.surface.luminance() > .5 - val color = getTopAppBarColor(title) + val (color, scrolledColor) = getTopAppBarColor(title) SideEffect { val activity = view.context as Activity @@ -66,7 +66,7 @@ fun YokaiScaffold( // modifier = Modifier.statusBarsPadding(), colors = topAppBarColors( containerColor = color, - scrolledContainerColor = color, + scrolledContainerColor = scrolledColor, ), navigationIcon = { ToolTipButton( @@ -85,7 +85,7 @@ fun YokaiScaffold( // modifier = Modifier.statusBarsPadding(), colors = topAppBarColors( containerColor = color, - scrolledContainerColor = color, + scrolledContainerColor = scrolledColor, ), navigationIcon = { ToolTipButton( @@ -104,10 +104,10 @@ fun YokaiScaffold( } @Composable -fun getTopAppBarColor(title: String): Color { +fun getTopAppBarColor(title: String): Pair { return when (title.isEmpty()) { - true -> Color.Transparent - false -> MaterialTheme.colorScheme.surface + true -> Color.Transparent to Color.Transparent + false -> MaterialTheme.colorScheme.surface to MaterialTheme.colorScheme.surfaceContainer } } From 71dcb2ab85cacaf18ae3e24782f7d9382fcfd089 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 27 Dec 2024 07:01:35 +0700 Subject: [PATCH 032/181] fix(AppBar): Adjust scrolled container color --- .../tachiyomi/ui/library/LibraryGridHolder.kt | 2 +- .../tachiyomi/ui/library/LibraryListHolder.kt | 2 +- .../tachiyomi/ui/manga/EditMangaDialog.kt | 4 ++-- .../tachiyomi/ui/manga/MangaHeaderHolder.kt | 2 +- .../tachiyomi/ui/migration/MangaHolder.kt | 2 +- .../manga/process/MigrationProcessHolder.kt | 2 +- .../tachiyomi/ui/recents/RecentMangaHolder.kt | 2 +- .../source/browse/BrowseSourceGridHolder.kt | 2 +- .../source/browse/BrowseSourceListHolder.kt | 2 +- .../globalsearch/GlobalSearchMangaHolder.kt | 2 +- .../main/java/yokai/presentation/Scaffold.kt | 14 +++++++------ .../presentation/component/TrackLogoIcon.kt | 2 +- .../component/preference/widget/InfoWidget.kt | 2 +- .../preference/widget/TextPreferenceWidget.kt | 2 +- .../presentation/onboarding/InfoScreen.kt | 2 +- .../presentation/reader/ChapterTransition.kt | 2 +- .../settings/screen/data/StorageInfo.kt | 2 +- .../main/java/yokai/util/ComposeExtensions.kt | 20 +++++++++++++++++++ .../core => }/util/ModifierExtensions.kt | 2 +- .../util/coil/ImageViewExtensions.kt | 2 +- app/src/main/res/values/styles.xml | 3 ++- 21 files changed, 49 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/yokai/util/ComposeExtensions.kt rename app/src/main/java/yokai/{presentation/core => }/util/ModifierExtensions.kt (94%) rename app/src/main/java/yokai/{presentation/core => }/util/coil/ImageViewExtensions.kt (97%) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt index 091642038b..abbcdc7224 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt @@ -23,7 +23,7 @@ import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.view.backgroundColor import eu.kanade.tachiyomi.util.view.setCards import eu.kanade.tachiyomi.widget.AutofitRecyclerView -import yokai.presentation.core.util.coil.loadManga +import yokai.util.coil.loadManga /** * Class used to hold the displayed data of a manga in the library, like the cover or the title. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt index 568068a324..38d17e926c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.util.lang.highlightText import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.view.setCards import yokai.i18n.MR -import yokai.presentation.core.util.coil.loadManga +import yokai.util.coil.loadManga import yokai.util.lang.getString /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt index be1f17a67e..f4dda11b1b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt @@ -43,8 +43,8 @@ import uy.kohesive.injekt.injectLazy import yokai.domain.manga.interactor.GetManga import yokai.domain.manga.models.cover import yokai.i18n.MR -import yokai.presentation.core.util.coil.asTarget -import yokai.presentation.core.util.coil.loadManga +import yokai.util.coil.asTarget +import yokai.util.coil.loadManga import yokai.util.lang.getString import android.R as AR diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt index 85e74a2599..9e21f6c4b3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt @@ -47,7 +47,7 @@ import eu.kanade.tachiyomi.util.system.isInNightMode import eu.kanade.tachiyomi.util.system.isLTR import eu.kanade.tachiyomi.util.view.resetStrokeColor import yokai.i18n.MR -import yokai.presentation.core.util.coil.loadManga +import yokai.util.coil.loadManga import yokai.util.lang.getString import android.R as AR diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt index 6732e191a5..d43c1dfc74 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt @@ -8,7 +8,7 @@ import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.databinding.MangaListItemBinding import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.util.view.setCards -import yokai.presentation.core.util.coil.loadManga +import yokai.util.coil.loadManga class MangaHolder( view: View, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt index bb57bc541c..392641714a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt @@ -27,7 +27,7 @@ import yokai.domain.chapter.interactor.GetChapter import yokai.domain.manga.interactor.GetManga import yokai.domain.manga.models.cover import yokai.i18n.MR -import yokai.presentation.core.util.coil.loadManga +import yokai.util.coil.loadManga import yokai.util.lang.getString class MigrationProcessHolder( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt index 24e003c2dc..dadc03fb3a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt @@ -34,7 +34,7 @@ import eu.kanade.tachiyomi.util.view.setCards import java.util.Date import java.util.concurrent.TimeUnit import yokai.i18n.MR -import yokai.presentation.core.util.coil.loadManga +import yokai.util.coil.loadManga import yokai.util.lang.getString import android.R as AR diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt index 50ed40ec23..92c31cb860 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt @@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter import eu.kanade.tachiyomi.util.view.setCards import yokai.domain.manga.models.cover -import yokai.presentation.core.util.coil.loadManga +import yokai.util.coil.loadManga /** * Class used to hold the displayed data of a manga in the library, like the cover or the title. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt index b76cfe7e8a..fcffcab813 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt @@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.databinding.MangaListItemBinding import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.util.view.setCards import yokai.domain.manga.models.cover -import yokai.presentation.core.util.coil.loadManga +import yokai.util.coil.loadManga /** * Class used to hold the displayed data of a manga in the catalogue, like the cover or the title. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt index e3837cf493..a2f98a2aa9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt @@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.view.makeShapeCorners import eu.kanade.tachiyomi.util.view.setCards import yokai.domain.manga.models.cover -import yokai.presentation.core.util.coil.loadManga +import yokai.util.coil.loadManga class GlobalSearchMangaHolder(view: View, adapter: GlobalSearchCardAdapter) : BaseFlexibleViewHolder(view, adapter) { diff --git a/app/src/main/java/yokai/presentation/Scaffold.kt b/app/src/main/java/yokai/presentation/Scaffold.kt index 6d411f5a91..8014c32e68 100644 --- a/app/src/main/java/yokai/presentation/Scaffold.kt +++ b/app/src/main/java/yokai/presentation/Scaffold.kt @@ -23,11 +23,13 @@ import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.unit.dp import androidx.core.view.WindowInsetsControllerCompat import dev.icerock.moko.resources.compose.stringResource import yokai.i18n.MR import yokai.presentation.component.ToolTipButton import yokai.presentation.core.ExpandedAppBar +import yokai.util.applyElevationOverlay @Composable fun YokaiScaffold( @@ -44,7 +46,7 @@ fun YokaiScaffold( ) { val view = LocalView.current val useDarkIcons = MaterialTheme.colorScheme.surface.luminance() > .5 - val (color, scrolledColor) = getTopAppBarColor(title) + val color = getTopAppBarColor(title) SideEffect { val activity = view.context as Activity @@ -66,7 +68,7 @@ fun YokaiScaffold( // modifier = Modifier.statusBarsPadding(), colors = topAppBarColors( containerColor = color, - scrolledContainerColor = scrolledColor, + scrolledContainerColor = color.applyElevationOverlay(4.dp), ), navigationIcon = { ToolTipButton( @@ -85,7 +87,7 @@ fun YokaiScaffold( // modifier = Modifier.statusBarsPadding(), colors = topAppBarColors( containerColor = color, - scrolledContainerColor = scrolledColor, + scrolledContainerColor = color.applyElevationOverlay(4.dp), ), navigationIcon = { ToolTipButton( @@ -104,10 +106,10 @@ fun YokaiScaffold( } @Composable -fun getTopAppBarColor(title: String): Pair { +fun getTopAppBarColor(title: String): Color { return when (title.isEmpty()) { - true -> Color.Transparent to Color.Transparent - false -> MaterialTheme.colorScheme.surface to MaterialTheme.colorScheme.surfaceContainer + true -> Color.Transparent + false -> MaterialTheme.colorScheme.surface } } diff --git a/app/src/main/java/yokai/presentation/component/TrackLogoIcon.kt b/app/src/main/java/yokai/presentation/component/TrackLogoIcon.kt index ca75eb8d7b..3a426e678a 100644 --- a/app/src/main/java/yokai/presentation/component/TrackLogoIcon.kt +++ b/app/src/main/java/yokai/presentation/component/TrackLogoIcon.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.data.track.TrackService -import yokai.presentation.core.util.clickableNoIndication +import yokai.util.clickableNoIndication @Composable fun TrackLogoIcon( diff --git a/app/src/main/java/yokai/presentation/component/preference/widget/InfoWidget.kt b/app/src/main/java/yokai/presentation/component/preference/widget/InfoWidget.kt index 334cfd0dde..833e034f9e 100644 --- a/app/src/main/java/yokai/presentation/component/preference/widget/InfoWidget.kt +++ b/app/src/main/java/yokai/presentation/component/preference/widget/InfoWidget.kt @@ -10,8 +10,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import yokai.presentation.core.util.secondaryItemAlpha import yokai.presentation.theme.Size +import yokai.util.secondaryItemAlpha @Composable internal fun InfoWidget(text: String) { diff --git a/app/src/main/java/yokai/presentation/component/preference/widget/TextPreferenceWidget.kt b/app/src/main/java/yokai/presentation/component/preference/widget/TextPreferenceWidget.kt index 8a4a677d76..4ed6ba6e6d 100644 --- a/app/src/main/java/yokai/presentation/component/preference/widget/TextPreferenceWidget.kt +++ b/app/src/main/java/yokai/presentation/component/preference/widget/TextPreferenceWidget.kt @@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import yokai.presentation.core.util.secondaryItemAlpha +import yokai.util.secondaryItemAlpha @Composable fun TextPreferenceWidget( diff --git a/app/src/main/java/yokai/presentation/onboarding/InfoScreen.kt b/app/src/main/java/yokai/presentation/onboarding/InfoScreen.kt index d26622c5dc..7671157a80 100644 --- a/app/src/main/java/yokai/presentation/onboarding/InfoScreen.kt +++ b/app/src/main/java/yokai/presentation/onboarding/InfoScreen.kt @@ -31,8 +31,8 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.zIndex -import yokai.presentation.core.util.secondaryItemAlpha import yokai.presentation.theme.Size +import yokai.util.secondaryItemAlpha @Composable fun InfoScreen( diff --git a/app/src/main/java/yokai/presentation/reader/ChapterTransition.kt b/app/src/main/java/yokai/presentation/reader/ChapterTransition.kt index ef6c0e3156..f51879ce76 100644 --- a/app/src/main/java/yokai/presentation/reader/ChapterTransition.kt +++ b/app/src/main/java/yokai/presentation/reader/ChapterTransition.kt @@ -42,7 +42,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.missingChapterCount import eu.kanade.tachiyomi.util.chapter.ChapterUtil.Companion.preferredChapterName import kotlinx.collections.immutable.persistentMapOf import yokai.i18n.MR -import yokai.presentation.core.util.secondaryItemAlpha +import yokai.util.secondaryItemAlpha @Composable fun ChapterTransition( diff --git a/app/src/main/java/yokai/presentation/settings/screen/data/StorageInfo.kt b/app/src/main/java/yokai/presentation/settings/screen/data/StorageInfo.kt index 827764ee5f..064a555b20 100644 --- a/app/src/main/java/yokai/presentation/settings/screen/data/StorageInfo.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/data/StorageInfo.kt @@ -18,9 +18,9 @@ import yokai.i18n.MR import yokai.util.lang.getString import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.util.storage.DiskUtil -import yokai.presentation.core.util.secondaryItemAlpha import yokai.presentation.theme.Size import yokai.presentation.theme.header +import yokai.util.secondaryItemAlpha import java.io.File @Composable diff --git a/app/src/main/java/yokai/util/ComposeExtensions.kt b/app/src/main/java/yokai/util/ComposeExtensions.kt new file mode 100644 index 0000000000..02fa8a9539 --- /dev/null +++ b/app/src/main/java/yokai/util/ComposeExtensions.kt @@ -0,0 +1,20 @@ +package yokai.util + +import androidx.compose.material3.LocalAbsoluteTonalElevation +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun Color.applyElevationOverlay( + elevation: Dp = 0.dp, + overlayColor: Color = MaterialTheme.colorScheme.secondary, +): Color { + val absoluteElevation = LocalAbsoluteTonalElevation.current + elevation + return overlayColor + .copy(alpha = (absoluteElevation.value) / 100f) + .compositeOver(this) +} diff --git a/app/src/main/java/yokai/presentation/core/util/ModifierExtensions.kt b/app/src/main/java/yokai/util/ModifierExtensions.kt similarity index 94% rename from app/src/main/java/yokai/presentation/core/util/ModifierExtensions.kt rename to app/src/main/java/yokai/util/ModifierExtensions.kt index 86116f4db1..491c77bae3 100644 --- a/app/src/main/java/yokai/presentation/core/util/ModifierExtensions.kt +++ b/app/src/main/java/yokai/util/ModifierExtensions.kt @@ -1,4 +1,4 @@ -package yokai.presentation.core.util +package yokai.util import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource diff --git a/app/src/main/java/yokai/presentation/core/util/coil/ImageViewExtensions.kt b/app/src/main/java/yokai/util/coil/ImageViewExtensions.kt similarity index 97% rename from app/src/main/java/yokai/presentation/core/util/coil/ImageViewExtensions.kt rename to app/src/main/java/yokai/util/coil/ImageViewExtensions.kt index 7f1f864ae5..40f1350410 100644 --- a/app/src/main/java/yokai/presentation/core/util/coil/ImageViewExtensions.kt +++ b/app/src/main/java/yokai/util/coil/ImageViewExtensions.kt @@ -1,4 +1,4 @@ -package yokai.presentation.core.util.coil +package yokai.util.coil import android.view.View import android.widget.ImageView diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b015ebe48a..0eb5a7108a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -8,6 +8,7 @@ @@ -395,4 +396,4 @@ 13sp - \ No newline at end of file + From 8a9d8166af1c150baae4bbc3a0a97d9e09d7e008 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 27 Dec 2024 07:42:06 +0700 Subject: [PATCH 033/181] style(AppBar): Partially revert 71dcb2ab but adjust the color to primaryContainer --- app/src/main/java/yokai/presentation/Scaffold.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/yokai/presentation/Scaffold.kt b/app/src/main/java/yokai/presentation/Scaffold.kt index 8014c32e68..cbc0b327d6 100644 --- a/app/src/main/java/yokai/presentation/Scaffold.kt +++ b/app/src/main/java/yokai/presentation/Scaffold.kt @@ -29,7 +29,6 @@ import dev.icerock.moko.resources.compose.stringResource import yokai.i18n.MR import yokai.presentation.component.ToolTipButton import yokai.presentation.core.ExpandedAppBar -import yokai.util.applyElevationOverlay @Composable fun YokaiScaffold( @@ -46,7 +45,7 @@ fun YokaiScaffold( ) { val view = LocalView.current val useDarkIcons = MaterialTheme.colorScheme.surface.luminance() > .5 - val color = getTopAppBarColor(title) + val (color, scrolledColor) = getTopAppBarColor(title) SideEffect { val activity = view.context as Activity @@ -68,7 +67,7 @@ fun YokaiScaffold( // modifier = Modifier.statusBarsPadding(), colors = topAppBarColors( containerColor = color, - scrolledContainerColor = color.applyElevationOverlay(4.dp), + scrolledContainerColor = scrolledColor, ), navigationIcon = { ToolTipButton( @@ -87,7 +86,7 @@ fun YokaiScaffold( // modifier = Modifier.statusBarsPadding(), colors = topAppBarColors( containerColor = color, - scrolledContainerColor = color.applyElevationOverlay(4.dp), + scrolledContainerColor = scrolledColor, ), navigationIcon = { ToolTipButton( @@ -106,10 +105,10 @@ fun YokaiScaffold( } @Composable -fun getTopAppBarColor(title: String): Color { +fun getTopAppBarColor(title: String): Pair { return when (title.isEmpty()) { - true -> Color.Transparent - false -> MaterialTheme.colorScheme.surface + true -> Color.Transparent to Color.Transparent + false -> MaterialTheme.colorScheme.surface to MaterialTheme.colorScheme.primaryContainer } } From 9bb869111dfcdcc3b558d271f3318b4c73e849db Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 27 Dec 2024 08:55:03 +0700 Subject: [PATCH 034/181] fix: Don't hardcode status bar color --- app/src/main/java/yokai/presentation/Scaffold.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/yokai/presentation/Scaffold.kt b/app/src/main/java/yokai/presentation/Scaffold.kt index cbc0b327d6..2eef0700ec 100644 --- a/app/src/main/java/yokai/presentation/Scaffold.kt +++ b/app/src/main/java/yokai/presentation/Scaffold.kt @@ -50,7 +50,7 @@ fun YokaiScaffold( SideEffect { val activity = view.context as Activity if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - activity.window.statusBarColor = color.toArgb() + //activity.window.statusBarColor = color.toArgb() WindowInsetsControllerCompat(activity.window, view).isAppearanceLightStatusBars = useDarkIcons } } From 310e90beb5eb0cd494a57b62797a66c4220075fb Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 27 Dec 2024 10:26:23 +0700 Subject: [PATCH 035/181] fix: Make status bar transparent --- app/src/main/java/yokai/presentation/Scaffold.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/yokai/presentation/Scaffold.kt b/app/src/main/java/yokai/presentation/Scaffold.kt index 2eef0700ec..c61e0daffc 100644 --- a/app/src/main/java/yokai/presentation/Scaffold.kt +++ b/app/src/main/java/yokai/presentation/Scaffold.kt @@ -50,7 +50,7 @@ fun YokaiScaffold( SideEffect { val activity = view.context as Activity if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - //activity.window.statusBarColor = color.toArgb() + activity.window.statusBarColor = Color.Transparent.toArgb() WindowInsetsControllerCompat(activity.window, view).isAppearanceLightStatusBars = useDarkIcons } } From 37f1f0e330b2766a7d1223bdc0cfa445dc8aff83 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sat, 28 Dec 2024 08:56:27 +0700 Subject: [PATCH 036/181] chore: Remove unused util function --- .../main/java/yokai/util/ComposeExtensions.kt | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 app/src/main/java/yokai/util/ComposeExtensions.kt diff --git a/app/src/main/java/yokai/util/ComposeExtensions.kt b/app/src/main/java/yokai/util/ComposeExtensions.kt deleted file mode 100644 index 02fa8a9539..0000000000 --- a/app/src/main/java/yokai/util/ComposeExtensions.kt +++ /dev/null @@ -1,20 +0,0 @@ -package yokai.util - -import androidx.compose.material3.LocalAbsoluteTonalElevation -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.compositeOver -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp - -@Composable -fun Color.applyElevationOverlay( - elevation: Dp = 0.dp, - overlayColor: Color = MaterialTheme.colorScheme.secondary, -): Color { - val absoluteElevation = LocalAbsoluteTonalElevation.current + elevation - return overlayColor - .copy(alpha = (absoluteElevation.value) / 100f) - .compositeOver(this) -} From cab40214d2bd4a650fe22886e2c822f4010a20fb Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sat, 28 Dec 2024 10:53:01 +0700 Subject: [PATCH 037/181] refactor: Use Compose for About page --- CHANGELOG.md | 1 + .../storage/preference/PreferenceExtension.kt | 8 + .../data/preference/PreferencesHelper.kt | 14 +- .../tachiyomi/ui/more/AboutController.kt | 195 +++----------- .../ui/more/AboutLicenseController.kt | 27 -- .../tachiyomi/ui/more/AboutLinksPreference.kt | 46 ---- .../controllers/debug/DebugController.kt | 6 +- .../source/browse/BrowseSourceController.kt | 5 +- .../main/java/yokai/presentation/Scaffold.kt | 12 +- .../component/icons/LocalSource.kt | 39 --- .../settings/SettingsCommonWidget.kt | 32 ++- .../about}/AboutLibraryLicenseScreen.kt | 2 +- .../screen/about}/AboutLicenseScreen.kt | 2 +- .../settings/screen/about/AboutScreen.kt | 239 ++++++++++++++++++ .../presentation/core/components/LinkIcon.kt | 31 +++ .../presentation/core/icons/CustomIcons.kt | 3 + .../yokai/presentation/core/icons/Discord.kt | 86 +++++++ .../yokai/presentation/core/icons/GitHub.kt | 68 +++++ .../presentation/core/icons/LocalSource.kt | 56 ++++ 19 files changed, 571 insertions(+), 301 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLinksPreference.kt delete mode 100644 app/src/main/java/yokai/presentation/component/icons/LocalSource.kt rename app/src/main/java/{eu/kanade/tachiyomi/ui/more => yokai/presentation/settings/screen/about}/AboutLibraryLicenseScreen.kt (98%) rename app/src/main/java/{eu/kanade/tachiyomi/ui/more => yokai/presentation/settings/screen/about}/AboutLicenseScreen.kt (97%) create mode 100644 app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt create mode 100644 presentation/core/src/main/java/yokai/presentation/core/components/LinkIcon.kt create mode 100644 presentation/core/src/main/java/yokai/presentation/core/icons/CustomIcons.kt create mode 100644 presentation/core/src/main/java/yokai/presentation/core/icons/Discord.kt create mode 100644 presentation/core/src/main/java/yokai/presentation/core/icons/GitHub.kt create mode 100644 presentation/core/src/main/java/yokai/presentation/core/icons/LocalSource.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index f1dacc1ab3..0f48dcf25f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Refactor EmptyView to use Compose - Refactor Reader ChapterTransition to use Compose (@arkon) - [Experimental] Add modified version of LargeTopAppBar that mimic J2K's ExpandedAppBarLayout +- Refactor About page to use Compose ## [1.9.7] diff --git a/app/src/main/java/eu/kanade/tachiyomi/core/storage/preference/PreferenceExtension.kt b/app/src/main/java/eu/kanade/tachiyomi/core/storage/preference/PreferenceExtension.kt index be0b8e9773..806b80ea22 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/core/storage/preference/PreferenceExtension.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/core/storage/preference/PreferenceExtension.kt @@ -5,9 +5,17 @@ import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember import eu.kanade.tachiyomi.core.preference.Preference +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Locale @Composable fun Preference.collectAsState(): State { val flow = remember(this) { changes() } return flow.collectAsState(initial = get()) } + +fun String.asDateFormat(): DateFormat = when (this) { + "" -> DateFormat.getDateInstance(DateFormat.SHORT) + else -> SimpleDateFormat(this, Locale.getDefault()) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index ec29bc9262..5d63f59b83 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.core.preference.Preference import eu.kanade.tachiyomi.core.preference.PreferenceStore import eu.kanade.tachiyomi.core.preference.getEnum +import eu.kanade.tachiyomi.core.storage.preference.asDateFormat import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.extension.model.InstalledExtensionsOrder @@ -19,13 +20,12 @@ import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation import eu.kanade.tachiyomi.ui.recents.RecentsPresenter import eu.kanade.tachiyomi.util.system.Themes +import java.text.DateFormat +import java.util.Locale import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.* import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values @@ -187,10 +187,10 @@ class PreferencesHelper(val context: Context, val preferenceStore: PreferenceSto fun anilistScoreType() = preferenceStore.getString("anilist_score_type", "POINT_10") - fun dateFormat(format: String = preferenceStore.getString(Keys.dateFormat, "").get()): DateFormat = when (format) { - "" -> DateFormat.getDateInstance(DateFormat.SHORT) - else -> SimpleDateFormat(format, Locale.getDefault()) - } + fun dateFormatRaw() = preferenceStore.getString(Keys.dateFormat, "") + + @Deprecated("Use dateFormatRaw().get().asDateFormat() instead") + fun dateFormat(format: String = dateFormatRaw().get()): DateFormat = format.asDateFormat() fun appLanguage() = preferenceStore.getString("app_language", "") diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt index 5fd462890a..4d1f9a63c8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt @@ -1,173 +1,53 @@ package eu.kanade.tachiyomi.ui.more import android.app.Dialog -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Intent import android.os.Build import android.os.Bundle import android.text.method.LinkMovementMethod import android.view.View import android.widget.TextView -import androidx.core.content.getSystemService -import androidx.core.net.toUri -import androidx.preference.PreferenceScreen -import co.touchlab.kermit.Logger -import eu.kanade.tachiyomi.BuildConfig +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import cafe.adriel.voyager.core.stack.StackEvent +import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.transitions.ScreenTransition import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob -import eu.kanade.tachiyomi.data.updater.AppUpdateChecker -import eu.kanade.tachiyomi.data.updater.AppUpdateNotifier -import eu.kanade.tachiyomi.data.updater.AppUpdateResult -import eu.kanade.tachiyomi.data.updater.RELEASE_URL +import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController import eu.kanade.tachiyomi.ui.base.controller.DialogController -import eu.kanade.tachiyomi.ui.setting.SettingsLegacyController -import eu.kanade.tachiyomi.ui.setting.add -import eu.kanade.tachiyomi.ui.setting.onClick -import eu.kanade.tachiyomi.ui.setting.preference -import eu.kanade.tachiyomi.ui.setting.preferenceCategory -import eu.kanade.tachiyomi.ui.setting.titleMRes -import eu.kanade.tachiyomi.util.CrashLogUtil -import eu.kanade.tachiyomi.util.lang.toTimestampString -import eu.kanade.tachiyomi.util.system.isOnline -import eu.kanade.tachiyomi.util.system.localeContext +import eu.kanade.tachiyomi.util.compose.LocalAlertDialog +import eu.kanade.tachiyomi.util.compose.LocalBackPress import eu.kanade.tachiyomi.util.system.materialAlertDialog -import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.setNegativeButton import eu.kanade.tachiyomi.util.view.setPositiveButton import eu.kanade.tachiyomi.util.view.setTitle -import eu.kanade.tachiyomi.util.view.snack -import eu.kanade.tachiyomi.util.view.withFadeTransaction import io.noties.markwon.Markwon -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import soup.compose.material.motion.animation.materialSharedAxisZ +import yokai.domain.ComposableAlertDialog import yokai.i18n.MR -import yokai.util.lang.getString -import java.text.DateFormat -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.* +import yokai.presentation.settings.screen.about.AboutScreen import android.R as AR -class AboutController : SettingsLegacyController() { +class AboutController : BaseComposeController() { - /** - * Checks for new releases - */ - private val updateChecker by lazy { AppUpdateChecker() } - - private val dateFormat: DateFormat by lazy { - preferences.dateFormat() - } - - private val isUpdaterEnabled = BuildConfig.INCLUDE_UPDATER - - override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { - titleMRes = MR.strings.about - - preference { - key = "pref_whats_new" - titleMRes = MR.strings.whats_new_this_release - onClick { - val intent = Intent( - Intent.ACTION_VIEW, - if (BuildConfig.DEBUG) { - "https://github.com/null2264/yokai/commits/master" - } else { - RELEASE_URL - }.toUri(), - ) - startActivity(intent) - } - } - if (isUpdaterEnabled) { - preference { - key = "pref_check_for_updates" - titleMRes = MR.strings.check_for_updates - onClick { - if (activity!!.isOnline()) { - checkVersion() - } else { - activity!!.toast(MR.strings.no_network_connection) - } + @Composable + override fun ScreenContent() { + Navigator( + screen = AboutScreen { body, url, isBeta -> + NewUpdateDialogController(body, url, isBeta).showDialog(router) + }, + content = { + CompositionLocalProvider( + LocalAlertDialog provides ComposableAlertDialog(null), + LocalBackPress provides router::handleBack, + ) { + ScreenTransition( + navigator = it, + // FIXME: Mimic J2K's Conductor transition + transition = { materialSharedAxisZ(forward = it.lastEvent != StackEvent.Pop) }, + ) } - } - } - preference { - key = "pref_version" - titleMRes = MR.strings.version - summary = if (BuildConfig.DEBUG || BuildConfig.NIGHTLY) { - "r" + BuildConfig.COMMIT_COUNT - } else { - BuildConfig.VERSION_NAME - } - - onClick { - activity?.let { - val deviceInfo = CrashLogUtil(it.localeContext).getDebugInfo() - val clipboard = it.getSystemService()!! - val appInfo = it.getString(MR.strings.app_info) - clipboard.setPrimaryClip(ClipData.newPlainText(appInfo, deviceInfo)) - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - view?.snack(context.getString(MR.strings._copied_to_clipboard, appInfo)) - } - } - } - } - preference { - key = "pref_build_time" - titleMRes = MR.strings.build_time - summary = getFormattedBuildTime(dateFormat) - } - - preferenceCategory { - preference { - key = "pref_oss" - titleMRes = MR.strings.open_source_licenses - - onClick { - router.pushController(AboutLicenseController().withFadeTransaction()) - } - } - } - add(AboutLinksPreference(context)) - } - - /** - * Checks version and shows a user prompt if an update is available. - */ - private fun checkVersion() { - val activity = activity ?: return - - activity.toast(MR.strings.searching_for_updates) - viewScope.launch { - val result = try { - updateChecker.checkForUpdate(activity, true) - } catch (error: Exception) { - withContext(Dispatchers.Main) { - activity.toast(error.message) - Logger.e(error) { "Couldn't check new update" } - } - } - when (result) { - is AppUpdateResult.NewUpdate -> { - val body = result.release.info - val url = result.release.downloadLink - val isBeta = result.release.preRelease == true - - // Create confirmation window - withContext(Dispatchers.Main) { - AppUpdateNotifier.releasePageUrl = result.release.releaseLink - NewUpdateDialogController(body, url, isBeta).showDialog(router) - } - } - is AppUpdateResult.NoNewUpdate -> { - withContext(Dispatchers.Main) { - activity.toast(MR.strings.no_new_updates_available) - } - } - } - } + }, + ) } class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) { @@ -220,19 +100,4 @@ class AboutController : SettingsLegacyController() { const val IS_BETA = "NewUpdateDialogController.is_beta" } } - - companion object { - fun getFormattedBuildTime(dateFormat: DateFormat): String { - try { - val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.getDefault()) - inputDf.timeZone = TimeZone.getTimeZone("UTC") - val buildTime = - inputDf.parse(BuildConfig.BUILD_TIME) ?: return BuildConfig.BUILD_TIME - - return buildTime.toTimestampString(dateFormat) - } catch (e: ParseException) { - return BuildConfig.BUILD_TIME - } - } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseController.kt deleted file mode 100644 index 6a2ea78a8e..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseController.kt +++ /dev/null @@ -1,27 +0,0 @@ -package eu.kanade.tachiyomi.ui.more - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import cafe.adriel.voyager.core.stack.StackEvent -import cafe.adriel.voyager.navigator.Navigator -import cafe.adriel.voyager.transitions.ScreenTransition -import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController -import eu.kanade.tachiyomi.util.compose.LocalBackPress -import soup.compose.material.motion.animation.materialSharedAxisZ - -class AboutLicenseController : BaseComposeController() { - @Composable - override fun ScreenContent() { - Navigator( - screen = AboutLicenseScreen(), - content = { - CompositionLocalProvider(LocalBackPress provides router::handleBack) { - ScreenTransition( - navigator = it, - transition = { materialSharedAxisZ(forward = it.lastEvent != StackEvent.Pop) }, - ) - } - }, - ) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLinksPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLinksPreference.kt deleted file mode 100644 index df08c5852d..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLinksPreference.kt +++ /dev/null @@ -1,46 +0,0 @@ -package eu.kanade.tachiyomi.ui.more - -import android.content.Context -import android.util.AttributeSet -import androidx.preference.Preference -import androidx.preference.PreferenceViewHolder -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.util.system.openInBrowser -import eu.kanade.tachiyomi.util.view.compatToolTipText - -class AboutLinksPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - Preference(context, attrs) { - - init { - layoutResource = R.layout.pref_about_links - isSelectable = false - } - - override fun onBindViewHolder(holder: PreferenceViewHolder) { - super.onBindViewHolder(holder) - - /* - (holder.itemView as LinearLayout).apply { - checkHeightThen { - val childCount = (this.getChildAt(0) as ViewGroup).childCount - val childCount2 = (this.getChildAt(1) as ViewGroup).childCount - val fullCount = childCount + childCount2 - orientation = - if (width >= (56 * fullCount).dpToPx) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL - } - } - */ - holder.findViewById(R.id.btn_website).apply { - compatToolTipText = (contentDescription.toString()) - setOnClickListener { context.openInBrowser("https://mihon.app") } - } - holder.findViewById(R.id.btn_discord).apply { - compatToolTipText = (contentDescription.toString()) - setOnClickListener { context.openInBrowser("https://discord.gg/mihon") } - } - holder.findViewById(R.id.btn_github).apply { - compatToolTipText = (contentDescription.toString()) - setOnClickListener { context.openInBrowser("https://github.com/null2264/yokai") } - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/debug/DebugController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/debug/DebugController.kt index ea69af4080..503b715a41 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/debug/DebugController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/debug/DebugController.kt @@ -4,15 +4,15 @@ import android.os.Build import androidx.preference.PreferenceScreen import androidx.webkit.WebViewCompat import eu.kanade.tachiyomi.BuildConfig -import eu.kanade.tachiyomi.ui.more.AboutController import eu.kanade.tachiyomi.ui.setting.SettingsLegacyController import eu.kanade.tachiyomi.ui.setting.onClick import eu.kanade.tachiyomi.ui.setting.preference import eu.kanade.tachiyomi.ui.setting.preferenceCategory import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.view.withFadeTransaction -import yokai.i18n.MR import java.text.DateFormat +import yokai.i18n.MR +import yokai.presentation.settings.screen.about.getFormattedBuildTime class DebugController : SettingsLegacyController() { @@ -49,7 +49,7 @@ class DebugController : SettingsLegacyController() { preference { key = "pref_build_time" title = "Build Time" - summary = AboutController.getFormattedBuildTime(dateFormat) + summary = getFormattedBuildTime(dateFormat) } preference { key = "pref_webview_version" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt index 28b847bfca..e27e2eb2b7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt @@ -69,7 +69,8 @@ import kotlinx.coroutines.launch import uy.kohesive.injekt.injectLazy import yokai.domain.manga.interactor.GetManga import yokai.i18n.MR -import yokai.presentation.component.icons.LocalSource +import yokai.presentation.core.icons.CustomIcons +import yokai.presentation.core.icons.LocalSource import yokai.util.lang.getString /** @@ -610,7 +611,7 @@ open class BrowseSourceController(bundle: Bundle) : if (presenter.source is HttpSource) { Icons.Filled.ExploreOff } else { - Icons.Filled.LocalSource + CustomIcons.LocalSource }, message, actions, diff --git a/app/src/main/java/yokai/presentation/Scaffold.kt b/app/src/main/java/yokai/presentation/Scaffold.kt index c61e0daffc..7122df5f38 100644 --- a/app/src/main/java/yokai/presentation/Scaffold.kt +++ b/app/src/main/java/yokai/presentation/Scaffold.kt @@ -23,7 +23,6 @@ import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.unit.dp import androidx.core.view.WindowInsetsControllerCompat import dev.icerock.moko.resources.compose.stringResource import yokai.i18n.MR @@ -35,14 +34,16 @@ fun YokaiScaffold( onNavigationIconClicked: () -> Unit, modifier: Modifier = Modifier, title: String = "", - scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(state = rememberTopAppBarState()), + scrollBehavior: TopAppBarScrollBehavior? = null, fab: @Composable () -> Unit = {}, navigationIcon: ImageVector = Icons.AutoMirrored.Filled.ArrowBack, navigationIconLabel: String = stringResource(MR.strings.back), actions: @Composable RowScope.() -> Unit = {}, appBarType: AppBarType = AppBarType.LARGE, + snackbarHost: @Composable () -> Unit = {}, content: @Composable (PaddingValues) -> Unit, ) { + val scrollBehaviorOrDefault = scrollBehavior ?: TopAppBarDefaults.enterAlwaysScrollBehavior(state = rememberTopAppBarState()) val view = LocalView.current val useDarkIcons = MaterialTheme.colorScheme.surface.luminance() > .5 val (color, scrolledColor) = getTopAppBarColor(title) @@ -56,7 +57,7 @@ fun YokaiScaffold( } Scaffold( - modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + modifier = modifier.nestedScroll(scrollBehaviorOrDefault.nestedScrollConnection), floatingActionButton = fab, topBar = { when (appBarType) { @@ -76,7 +77,7 @@ fun YokaiScaffold( buttonClicked = onNavigationIconClicked, ) }, - scrollBehavior = scrollBehavior, + scrollBehavior = scrollBehaviorOrDefault, actions = actions, ) AppBarType.LARGE -> ExpandedAppBar( @@ -95,11 +96,12 @@ fun YokaiScaffold( buttonClicked = onNavigationIconClicked, ) }, - scrollBehavior = scrollBehavior, + scrollBehavior = scrollBehaviorOrDefault, actions = actions, ) } }, + snackbarHost = snackbarHost, content = content, ) } diff --git a/app/src/main/java/yokai/presentation/component/icons/LocalSource.kt b/app/src/main/java/yokai/presentation/component/icons/LocalSource.kt deleted file mode 100644 index 8d579b2cc1..0000000000 --- a/app/src/main/java/yokai/presentation/component/icons/LocalSource.kt +++ /dev/null @@ -1,39 +0,0 @@ -package yokai.presentation.component.icons - -import androidx.compose.material.icons.Icons -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.graphics.vector.path -import androidx.compose.ui.unit.dp - -private var _localSource: ImageVector? = null - -val Icons.Filled.LocalSource: ImageVector get() { - if (_localSource != null) return _localSource!! - _localSource = ImageVector.Builder( - name = "localSource", - defaultWidth = 24.0.dp, - defaultHeight = 24.0.dp, - viewportWidth = 24.0f, - viewportHeight = 24.0f, - ).apply { - path(fill = SolidColor(Color.Black)) { - moveTo(12f, 11.55f) - curveTo(9.64f, 9.35f, 6.48f, 8f, 3f, 8f) - verticalLineToRelative(11f) - curveToRelative(3.48f, 0f, 6.64f, 1.35f, 9f, 3.55f) - curveToRelative(2.36f, -2.19f, 5.52f, -3.55f, 9f, -3.55f) - verticalLineTo(8f) - curveToRelative(-3.48f, 0f, -6.64f, 1.35f, -9f, 3.55f) - close() - moveTo(12f, 8f) - curveToRelative(1.66f, 0f, 3f, -1.34f, 3f, -3f) - reflectiveCurveToRelative(-1.34f, -3f, -3f, -3f) - reflectiveCurveToRelative(-3f, 1.34f, -3f, 3f) - reflectiveCurveToRelative(1.34f, 3f, 3f, 3f) - close() - } - }.build() - return _localSource!! -} diff --git a/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt b/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt index cff9066e2a..7fc0fdb185 100644 --- a/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt +++ b/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -35,11 +36,12 @@ fun SettingsScaffold( title: String, appBarType: AppBarType? = null, appBarActions: @Composable RowScope.() -> Unit = {}, - itemsProvider: @Composable () -> List, + appBarScrollBehavior: TopAppBarScrollBehavior? = null, + snackbarHost: @Composable () -> Unit = {}, + content: @Composable (PaddingValues) -> Unit, ) { val preferences: PreferencesHelper by injectLazy() val useLargeAppBar by preferences.useLargeToolbar().collectAsState() - val listState = rememberLazyListState() val onBackPress = LocalBackPress.currentOrThrow val alertDialog = LocalAlertDialog.currentOrThrow @@ -48,14 +50,34 @@ fun SettingsScaffold( title = title, appBarType = appBarType ?: if (useLargeAppBar) AppBarType.LARGE else AppBarType.SMALL, actions = appBarActions, - scrollBehavior = enterAlwaysCollapsedScrollBehavior( + scrollBehavior = appBarScrollBehavior, + snackbarHost = snackbarHost, + ) { innerPadding -> + alertDialog.content?.let { it() } + + content(innerPadding) + } +} + +@Composable +fun SettingsScaffold( + title: String, + appBarType: AppBarType? = null, + appBarActions: @Composable RowScope.() -> Unit = {}, + itemsProvider: @Composable () -> List, +) { + val listState = rememberLazyListState() + + SettingsScaffold( + title = title, + appBarType = appBarType, + appBarActions = appBarActions, + appBarScrollBehavior = enterAlwaysCollapsedScrollBehavior( state = rememberTopAppBarState(), canScroll = { listState.canScrollForward || listState.canScrollBackward }, isAtTop = { listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 }, ), ) { innerPadding -> - alertDialog.content?.let { it() } - PreferenceScreen( items = itemsProvider(), listState = listState, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLibraryLicenseScreen.kt b/app/src/main/java/yokai/presentation/settings/screen/about/AboutLibraryLicenseScreen.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLibraryLicenseScreen.kt rename to app/src/main/java/yokai/presentation/settings/screen/about/AboutLibraryLicenseScreen.kt index 11b2e7c66d..7ed11d10d8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLibraryLicenseScreen.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/about/AboutLibraryLicenseScreen.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.more +package yokai.presentation.settings.screen.about import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseScreen.kt b/app/src/main/java/yokai/presentation/settings/screen/about/AboutLicenseScreen.kt similarity index 97% rename from app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseScreen.kt rename to app/src/main/java/yokai/presentation/settings/screen/about/AboutLicenseScreen.kt index c98bc48cfa..6d4ca8be2a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLicenseScreen.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/about/AboutLicenseScreen.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.more +package yokai.presentation.settings.screen.about import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.TopAppBarDefaults diff --git a/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt new file mode 100644 index 0000000000..d70460d156 --- /dev/null +++ b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt @@ -0,0 +1,239 @@ +package yokai.presentation.settings.screen.about + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.os.Build +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Public +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.unit.dp +import androidx.core.content.getSystemService +import cafe.adriel.voyager.navigator.LocalNavigator +import co.touchlab.kermit.Logger +import dev.icerock.moko.resources.compose.stringResource +import eu.kanade.tachiyomi.BuildConfig +import eu.kanade.tachiyomi.core.storage.preference.asDateFormat +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.updater.AppUpdateChecker +import eu.kanade.tachiyomi.data.updater.AppUpdateNotifier +import eu.kanade.tachiyomi.data.updater.AppUpdateResult +import eu.kanade.tachiyomi.data.updater.RELEASE_URL +import eu.kanade.tachiyomi.util.CrashLogUtil +import eu.kanade.tachiyomi.util.compose.currentOrThrow +import eu.kanade.tachiyomi.util.lang.toTimestampString +import eu.kanade.tachiyomi.util.system.isOnline +import eu.kanade.tachiyomi.util.system.localeContext +import eu.kanade.tachiyomi.util.system.toast +import eu.kanade.tachiyomi.util.system.withUIContext +import java.text.DateFormat +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Locale +import java.util.TimeZone +import kotlinx.coroutines.launch +import uy.kohesive.injekt.injectLazy +import yokai.i18n.MR +import yokai.presentation.component.preference.widget.TextPreferenceWidget +import yokai.presentation.core.components.LinkIcon +import yokai.presentation.core.enterAlwaysCollapsedScrollBehavior +import yokai.presentation.core.icons.CustomIcons +import yokai.presentation.core.icons.Discord +import yokai.presentation.core.icons.GitHub +import yokai.presentation.settings.SettingsScaffold +import yokai.util.Screen +import yokai.util.lang.getString + +class AboutScreen(private val showNewUpdateDialog: (String, String, Boolean?) -> Unit) : Screen() { + @Composable + override fun Content() { + val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + val uriHandler = LocalUriHandler.current + + val snackbarHostState = remember { SnackbarHostState() } + val scope = rememberCoroutineScope() + val listState = rememberLazyListState() + + val preferences: PreferencesHelper by injectLazy() + val dateFormat by lazy { preferences.dateFormatRaw().get().asDateFormat() } + + SettingsScaffold( + title = stringResource(MR.strings.about), + snackbarHost = { + SnackbarHost(hostState = snackbarHostState) + }, + appBarScrollBehavior = enterAlwaysCollapsedScrollBehavior( + state = rememberTopAppBarState(), + canScroll = { listState.canScrollForward || listState.canScrollBackward }, + isAtTop = { listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 }, + ), + content = { contentPadding -> + LazyColumn( + contentPadding = contentPadding, + state = listState, + ) { + item { + TextPreferenceWidget( + title = stringResource(MR.strings.whats_new_this_release), + onPreferenceClick = { + uriHandler.openUri(if (BuildConfig.DEBUG) SOURCE_URL else RELEASE_URL) + }, + ) + } + + if (BuildConfig.INCLUDE_UPDATER) { + item { + TextPreferenceWidget( + title = stringResource(MR.strings.check_for_updates), + onPreferenceClick = { + if (context.isOnline()) { + scope.launch { + context.checkVersion() + } + } else { + context.toast(MR.strings.no_network_connection) + } + }, + ) + } + } + + item { + TextPreferenceWidget( + title = stringResource(MR.strings.version), + subtitle = getVersionName(), + onPreferenceClick = { + val deviceInfo = CrashLogUtil(context.localeContext).getDebugInfo() + val clipboard = context.getSystemService()!! + val appInfo = context.getString(MR.strings.app_info) + clipboard.setPrimaryClip(ClipData.newPlainText(appInfo, deviceInfo)) + scope.launch { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + snackbarHostState.showSnackbar( + message = context.getString(MR.strings._copied_to_clipboard, appInfo), + ) + } + } + }, + ) + } + + item { + TextPreferenceWidget( + title = stringResource(MR.strings.version), + subtitle = getFormattedBuildTime(dateFormat), + ) + } + + item { + Column(modifier = Modifier.fillMaxWidth()) { + HorizontalDivider() + + TextPreferenceWidget( + title = stringResource(MR.strings.open_source_licenses), + onPreferenceClick = { navigator.push(AboutLicenseScreen()) } + ) + } + } + + item { + FlowRow( + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + horizontalArrangement = Arrangement.Center, + ) { + LinkIcon( + label = "Website", + icon = Icons.Outlined.Public, + url = "https://mihon.app", + ) + LinkIcon( + label = "Discord", + icon = CustomIcons.Discord, + url = "https://discord.gg/mihon", + ) + LinkIcon( + label = "GitHub", + icon = CustomIcons.GitHub, + url = "https://github.com/null2264/yokai", + ) + } + } + } + }, + ) + } + + private fun getVersionName(): String = when { + BuildConfig.DEBUG -> "Debug ${BuildConfig.COMMIT_SHA}" + BuildConfig.NIGHTLY -> "Nightly ${BuildConfig.COMMIT_COUNT} (${BuildConfig.COMMIT_SHA})" + else -> "Release ${BuildConfig.VERSION_NAME}" + } + + private suspend fun Context.checkVersion() { + val updateChecker = AppUpdateChecker() + + withUIContext { toast(MR.strings.searching_for_updates) } + + val result = try { + updateChecker.checkForUpdate(this, true) + } catch (error: Exception) { + withUIContext { + toast(error.message) + Logger.e(error) { "Couldn't check new update" } + } + } + when (result) { + is AppUpdateResult.NewUpdate -> { + val body = result.release.info + val url = result.release.downloadLink + val isBeta = result.release.preRelease == true + + // Create confirmation window + withUIContext { + AppUpdateNotifier.releasePageUrl = result.release.releaseLink + showNewUpdateDialog(body, url, isBeta) + } + } + is AppUpdateResult.NoNewUpdate -> { + withUIContext { + toast(MR.strings.no_new_updates_available) + } + } + } + } +} + +fun getFormattedBuildTime(dateFormat: DateFormat): String { + try { + val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.getDefault()) + inputDf.timeZone = TimeZone.getTimeZone("UTC") + val buildTime = + inputDf.parse(BuildConfig.BUILD_TIME) ?: return BuildConfig.BUILD_TIME + + return buildTime.toTimestampString(dateFormat) + } catch (e: ParseException) { + return BuildConfig.BUILD_TIME + } +} + +private const val SOURCE_URL = "https://github.com/null2264/yokai/commits/master" diff --git a/presentation/core/src/main/java/yokai/presentation/core/components/LinkIcon.kt b/presentation/core/src/main/java/yokai/presentation/core/components/LinkIcon.kt new file mode 100644 index 0000000000..03d02b421d --- /dev/null +++ b/presentation/core/src/main/java/yokai/presentation/core/components/LinkIcon.kt @@ -0,0 +1,31 @@ +package yokai.presentation.core.components + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.unit.dp + +@Composable +fun LinkIcon( + label: String, + icon: ImageVector, + url: String, + modifier: Modifier = Modifier, +) { + val uriHandler = LocalUriHandler.current + IconButton( + modifier = modifier.padding(4.dp), + onClick = { uriHandler.openUri(url) }, + ) { + Icon( + imageVector = icon, + tint = MaterialTheme.colorScheme.primary, + contentDescription = label, + ) + } +} diff --git a/presentation/core/src/main/java/yokai/presentation/core/icons/CustomIcons.kt b/presentation/core/src/main/java/yokai/presentation/core/icons/CustomIcons.kt new file mode 100644 index 0000000000..6b6ce894a2 --- /dev/null +++ b/presentation/core/src/main/java/yokai/presentation/core/icons/CustomIcons.kt @@ -0,0 +1,3 @@ +package yokai.presentation.core.icons + +object CustomIcons diff --git a/presentation/core/src/main/java/yokai/presentation/core/icons/Discord.kt b/presentation/core/src/main/java/yokai/presentation/core/icons/Discord.kt new file mode 100644 index 0000000000..5eb4d368ee --- /dev/null +++ b/presentation/core/src/main/java/yokai/presentation/core/icons/Discord.kt @@ -0,0 +1,86 @@ +package yokai.presentation.core.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +@Suppress("UnusedReceiverParameter", "BooleanLiteralArgument") +val CustomIcons.Discord: ImageVector + get() { + if (_discord != null) { + return _discord!! + } + _discord = Builder( + name = "Discord", + defaultWidth = 24.0.dp, + defaultHeight = 24.0.dp, + viewportWidth = 24.0f, + viewportHeight = 24.0f, + ).apply { + path( + fill = SolidColor(Color(0xFF000000)), + stroke = null, + strokeLineWidth = 0.0f, + strokeLineCap = Butt, + strokeLineJoin = Miter, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(20.317f, 4.3698f) + arcToRelative(19.7913f, 19.7913f, 0.0f, false, false, -4.8851f, -1.5152f) + arcToRelative(0.0741f, 0.0741f, 0.0f, false, false, -0.0785f, 0.0371f) + curveToRelative(-0.211f, 0.3753f, -0.4447f, 0.8648f, -0.6083f, 1.2495f) + curveToRelative(-1.8447f, -0.2762f, -3.68f, -0.2762f, -5.4868f, 0.0f) + curveToRelative(-0.1636f, -0.3933f, -0.4058f, -0.8742f, -0.6177f, -1.2495f) + arcToRelative(0.077f, 0.077f, 0.0f, false, false, -0.0785f, -0.037f) + arcToRelative(19.7363f, 19.7363f, 0.0f, false, false, -4.8852f, 1.515f) + arcToRelative(0.0699f, 0.0699f, 0.0f, false, false, -0.0321f, 0.0277f) + curveTo(0.5334f, 9.0458f, -0.319f, 13.5799f, 0.0992f, 18.0578f) + arcToRelative(0.0824f, 0.0824f, 0.0f, false, false, 0.0312f, 0.0561f) + curveToRelative(2.0528f, 1.5076f, 4.0413f, 2.4228f, 5.9929f, 3.0294f) + arcToRelative(0.0777f, 0.0777f, 0.0f, false, false, 0.0842f, -0.0276f) + curveToRelative(0.4616f, -0.6304f, 0.8731f, -1.2952f, 1.226f, -1.9942f) + arcToRelative(0.076f, 0.076f, 0.0f, false, false, -0.0416f, -0.1057f) + curveToRelative(-0.6528f, -0.2476f, -1.2743f, -0.5495f, -1.8722f, -0.8923f) + arcToRelative(0.077f, 0.077f, 0.0f, false, true, -0.0076f, -0.1277f) + curveToRelative(0.1258f, -0.0943f, 0.2517f, -0.1923f, 0.3718f, -0.2914f) + arcToRelative(0.0743f, 0.0743f, 0.0f, false, true, 0.0776f, -0.0105f) + curveToRelative(3.9278f, 1.7933f, 8.18f, 1.7933f, 12.0614f, 0.0f) + arcToRelative(0.0739f, 0.0739f, 0.0f, false, true, 0.0785f, 0.0095f) + curveToRelative(0.1202f, 0.099f, 0.246f, 0.1981f, 0.3728f, 0.2924f) + arcToRelative(0.077f, 0.077f, 0.0f, false, true, -0.0066f, 0.1276f) + arcToRelative(12.2986f, 12.2986f, 0.0f, false, true, -1.873f, 0.8914f) + arcToRelative(0.0766f, 0.0766f, 0.0f, false, false, -0.0407f, 0.1067f) + curveToRelative(0.3604f, 0.698f, 0.7719f, 1.3628f, 1.225f, 1.9932f) + arcToRelative(0.076f, 0.076f, 0.0f, false, false, 0.0842f, 0.0286f) + curveToRelative(1.961f, -0.6067f, 3.9495f, -1.5219f, 6.0023f, -3.0294f) + arcToRelative(0.077f, 0.077f, 0.0f, false, false, 0.0313f, -0.0552f) + curveToRelative(0.5004f, -5.177f, -0.8382f, -9.6739f, -3.5485f, -13.6604f) + arcToRelative(0.061f, 0.061f, 0.0f, false, false, -0.0312f, -0.0286f) + close() + moveTo(8.02f, 15.3312f) + curveToRelative(-1.1825f, 0.0f, -2.1569f, -1.0857f, -2.1569f, -2.419f) + curveToRelative(0.0f, -1.3332f, 0.9555f, -2.4189f, 2.157f, -2.4189f) + curveToRelative(1.2108f, 0.0f, 2.1757f, 1.0952f, 2.1568f, 2.419f) + curveToRelative(0.0f, 1.3332f, -0.9555f, 2.4189f, -2.1569f, 2.4189f) + close() + moveTo(15.9948f, 15.3312f) + curveToRelative(-1.1825f, 0.0f, -2.1569f, -1.0857f, -2.1569f, -2.419f) + curveToRelative(0.0f, -1.3332f, 0.9554f, -2.4189f, 2.1569f, -2.4189f) + curveToRelative(1.2108f, 0.0f, 2.1757f, 1.0952f, 2.1568f, 2.419f) + curveToRelative(0.0f, 1.3332f, -0.946f, 2.4189f, -2.1568f, 2.4189f) + close() + } + } + .build() + return _discord!! + } + +@Suppress("ObjectPropertyName") +private var _discord: ImageVector? = null diff --git a/presentation/core/src/main/java/yokai/presentation/core/icons/GitHub.kt b/presentation/core/src/main/java/yokai/presentation/core/icons/GitHub.kt new file mode 100644 index 0000000000..5115f5d0ff --- /dev/null +++ b/presentation/core/src/main/java/yokai/presentation/core/icons/GitHub.kt @@ -0,0 +1,68 @@ +package yokai.presentation.core.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +@Suppress("UnusedReceiverParameter") +val CustomIcons.GitHub: ImageVector + get() { + if (_github != null) { + return _github!! + } + _github = Builder( + name = "GitHub", + defaultWidth = 24.0.dp, + defaultHeight = 24.0.dp, + viewportWidth = 24.0f, + viewportHeight = 24.0f, + ).apply { + path( + fill = SolidColor(Color(0xFF000000)), + stroke = null, + strokeLineWidth = 0.0f, + strokeLineCap = Butt, + strokeLineJoin = Miter, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(12.0f, 0.297f) + curveToRelative(-6.63f, 0.0f, -12.0f, 5.373f, -12.0f, 12.0f) + curveToRelative(0.0f, 5.303f, 3.438f, 9.8f, 8.205f, 11.385f) + curveToRelative(0.6f, 0.113f, 0.82f, -0.258f, 0.82f, -0.577f) + curveToRelative(0.0f, -0.285f, -0.01f, -1.04f, -0.015f, -2.04f) + curveToRelative(-3.338f, 0.724f, -4.042f, -1.61f, -4.042f, -1.61f) + curveTo(4.422f, 18.07f, 3.633f, 17.7f, 3.633f, 17.7f) + curveToRelative(-1.087f, -0.744f, 0.084f, -0.729f, 0.084f, -0.729f) + curveToRelative(1.205f, 0.084f, 1.838f, 1.236f, 1.838f, 1.236f) + curveToRelative(1.07f, 1.835f, 2.809f, 1.305f, 3.495f, 0.998f) + curveToRelative(0.108f, -0.776f, 0.417f, -1.305f, 0.76f, -1.605f) + curveToRelative(-2.665f, -0.3f, -5.466f, -1.332f, -5.466f, -5.93f) + curveToRelative(0.0f, -1.31f, 0.465f, -2.38f, 1.235f, -3.22f) + curveToRelative(-0.135f, -0.303f, -0.54f, -1.523f, 0.105f, -3.176f) + curveToRelative(0.0f, 0.0f, 1.005f, -0.322f, 3.3f, 1.23f) + curveToRelative(0.96f, -0.267f, 1.98f, -0.399f, 3.0f, -0.405f) + curveToRelative(1.02f, 0.006f, 2.04f, 0.138f, 3.0f, 0.405f) + curveToRelative(2.28f, -1.552f, 3.285f, -1.23f, 3.285f, -1.23f) + curveToRelative(0.645f, 1.653f, 0.24f, 2.873f, 0.12f, 3.176f) + curveToRelative(0.765f, 0.84f, 1.23f, 1.91f, 1.23f, 3.22f) + curveToRelative(0.0f, 4.61f, -2.805f, 5.625f, -5.475f, 5.92f) + curveToRelative(0.42f, 0.36f, 0.81f, 1.096f, 0.81f, 2.22f) + curveToRelative(0.0f, 1.606f, -0.015f, 2.896f, -0.015f, 3.286f) + curveToRelative(0.0f, 0.315f, 0.21f, 0.69f, 0.825f, 0.57f) + curveTo(20.565f, 22.092f, 24.0f, 17.592f, 24.0f, 12.297f) + curveToRelative(0.0f, -6.627f, -5.373f, -12.0f, -12.0f, -12.0f) + } + } + .build() + return _github!! + } + +@Suppress("ObjectPropertyName") +private var _github: ImageVector? = null diff --git a/presentation/core/src/main/java/yokai/presentation/core/icons/LocalSource.kt b/presentation/core/src/main/java/yokai/presentation/core/icons/LocalSource.kt new file mode 100644 index 0000000000..b45c727597 --- /dev/null +++ b/presentation/core/src/main/java/yokai/presentation/core/icons/LocalSource.kt @@ -0,0 +1,56 @@ +package yokai.presentation.core.icons + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathFillType.Companion.NonZero +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.StrokeCap.Companion.Butt +import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.ImageVector.Builder +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.unit.dp + +@Suppress("UnusedReceiverParameter") +val CustomIcons.LocalSource: ImageVector + get() { + if (_localSource != null) { + return _localSource!! + } + _localSource = Builder( + name = "localSource", + defaultWidth = 24.0.dp, + defaultHeight = 24.0.dp, + viewportWidth = 24.0f, + viewportHeight = 24.0f, + ).apply { + path( + fill = SolidColor(Color(0xFF000000)), + stroke = null, + strokeLineWidth = 0.0f, + strokeLineCap = Butt, + strokeLineJoin = Miter, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(12f, 11.55f) + curveTo(9.64f, 9.35f, 6.48f, 8f, 3f, 8f) + verticalLineToRelative(11f) + curveToRelative(3.48f, 0f, 6.64f, 1.35f, 9f, 3.55f) + curveToRelative(2.36f, -2.19f, 5.52f, -3.55f, 9f, -3.55f) + verticalLineTo(8f) + curveToRelative(-3.48f, 0f, -6.64f, 1.35f, -9f, 3.55f) + close() + moveTo(12f, 8f) + curveToRelative(1.66f, 0f, 3f, -1.34f, 3f, -3f) + reflectiveCurveToRelative(-1.34f, -3f, -3f, -3f) + reflectiveCurveToRelative(-3f, 1.34f, -3f, 3f) + reflectiveCurveToRelative(1.34f, 3f, 3f, 3f) + close() + } + } + .build() + return _localSource!! + } + +@Suppress("ObjectPropertyName") +private var _localSource: ImageVector? = null From c6a86d773b2042f0cc873bbd812fcbe3e3e9bdc2 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sat, 28 Dec 2024 11:08:48 +0700 Subject: [PATCH 038/181] fix: Copy paste moment, it's Build time not Version --- .../yokai/presentation/settings/screen/about/AboutScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt index d70460d156..ca3b495904 100644 --- a/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt @@ -137,7 +137,7 @@ class AboutScreen(private val showNewUpdateDialog: (String, String, Boolean?) -> item { TextPreferenceWidget( - title = stringResource(MR.strings.version), + title = stringResource(MR.strings.build_time), subtitle = getFormattedBuildTime(dateFormat), ) } From cd4079aa4b0daba99e32e04af3101942130df3e6 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sat, 28 Dec 2024 11:23:34 +0700 Subject: [PATCH 039/181] fix: Crashes caused by cab40214d2bd4a650fe22886e2c822f4010a20fb Screen arguments need to be parcelable --- .../eu/kanade/tachiyomi/ui/more/AboutController.kt | 6 +++--- .../java/eu/kanade/tachiyomi/util/compose/Locals.kt | 6 ++++++ .../settings/screen/about/AboutScreen.kt | 12 ++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt index 4d1f9a63c8..e0635b44c0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt @@ -16,6 +16,7 @@ import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.util.compose.LocalAlertDialog import eu.kanade.tachiyomi.util.compose.LocalBackPress +import eu.kanade.tachiyomi.util.compose.LocalRouter import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.view.setNegativeButton import eu.kanade.tachiyomi.util.view.setPositiveButton @@ -32,13 +33,12 @@ class AboutController : BaseComposeController() { @Composable override fun ScreenContent() { Navigator( - screen = AboutScreen { body, url, isBeta -> - NewUpdateDialogController(body, url, isBeta).showDialog(router) - }, + screen = AboutScreen(), content = { CompositionLocalProvider( LocalAlertDialog provides ComposableAlertDialog(null), LocalBackPress provides router::handleBack, + LocalRouter provides router, ) { ScreenTransition( navigator = it, diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/compose/Locals.kt b/app/src/main/java/eu/kanade/tachiyomi/util/compose/Locals.kt index d9590305dd..54a1468710 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/compose/Locals.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/compose/Locals.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf +import com.bluelinelabs.conductor.Router import yokai.domain.ComposableAlertDialog val ProvidableCompositionLocal.currentOrThrow @@ -12,3 +13,8 @@ val ProvidableCompositionLocal.currentOrThrow val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null } val LocalAlertDialog: ProvidableCompositionLocal = compositionLocalOf { null } +@Deprecated( + message = "Scheduled for removal once Conductor is fully replaced by Voyager", + replaceWith = ReplaceWith("LocalNavigator", "cafe.adriel.voyager.navigator.LocalNavigator"), +) +val LocalRouter: ProvidableCompositionLocal = compositionLocalOf { null } diff --git a/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt index ca3b495904..66d57832dc 100644 --- a/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.unit.dp import androidx.core.content.getSystemService import cafe.adriel.voyager.navigator.LocalNavigator import co.touchlab.kermit.Logger +import com.bluelinelabs.conductor.Router import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.core.storage.preference.asDateFormat @@ -35,7 +36,9 @@ import eu.kanade.tachiyomi.data.updater.AppUpdateChecker import eu.kanade.tachiyomi.data.updater.AppUpdateNotifier import eu.kanade.tachiyomi.data.updater.AppUpdateResult import eu.kanade.tachiyomi.data.updater.RELEASE_URL +import eu.kanade.tachiyomi.ui.more.AboutController.NewUpdateDialogController import eu.kanade.tachiyomi.util.CrashLogUtil +import eu.kanade.tachiyomi.util.compose.LocalRouter import eu.kanade.tachiyomi.util.compose.currentOrThrow import eu.kanade.tachiyomi.util.lang.toTimestampString import eu.kanade.tachiyomi.util.system.isOnline @@ -60,11 +63,12 @@ import yokai.presentation.settings.SettingsScaffold import yokai.util.Screen import yokai.util.lang.getString -class AboutScreen(private val showNewUpdateDialog: (String, String, Boolean?) -> Unit) : Screen() { +class AboutScreen : Screen() { @Composable override fun Content() { val context = LocalContext.current val navigator = LocalNavigator.currentOrThrow + val router = LocalRouter.currentOrThrow val uriHandler = LocalUriHandler.current val snackbarHostState = remember { SnackbarHostState() } @@ -105,7 +109,7 @@ class AboutScreen(private val showNewUpdateDialog: (String, String, Boolean?) -> onPreferenceClick = { if (context.isOnline()) { scope.launch { - context.checkVersion() + context.checkVersion(router) } } else { context.toast(MR.strings.no_network_connection) @@ -189,7 +193,7 @@ class AboutScreen(private val showNewUpdateDialog: (String, String, Boolean?) -> else -> "Release ${BuildConfig.VERSION_NAME}" } - private suspend fun Context.checkVersion() { + private suspend fun Context.checkVersion(router: Router) { val updateChecker = AppUpdateChecker() withUIContext { toast(MR.strings.searching_for_updates) } @@ -211,7 +215,7 @@ class AboutScreen(private val showNewUpdateDialog: (String, String, Boolean?) -> // Create confirmation window withUIContext { AppUpdateNotifier.releasePageUrl = result.release.releaseLink - showNewUpdateDialog(body, url, isBeta) + NewUpdateDialogController(body, url, isBeta).showDialog(router) } } is AppUpdateResult.NoNewUpdate -> { From 67c4500ccef72c468fbd4d8dbaea8b99bb4c3e87 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sat, 28 Dec 2024 12:15:08 +0700 Subject: [PATCH 040/181] style(about): Use CrossfadeTransition It's basically the same as J2K's .withFadeTransaction --- .../java/eu/kanade/tachiyomi/ui/more/AboutController.kt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt index e0635b44c0..4c55cbef88 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt @@ -10,7 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator -import cafe.adriel.voyager.transitions.ScreenTransition +import cafe.adriel.voyager.transitions.CrossfadeTransition import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController import eu.kanade.tachiyomi.ui.base.controller.DialogController @@ -22,7 +22,6 @@ import eu.kanade.tachiyomi.util.view.setNegativeButton import eu.kanade.tachiyomi.util.view.setPositiveButton import eu.kanade.tachiyomi.util.view.setTitle import io.noties.markwon.Markwon -import soup.compose.material.motion.animation.materialSharedAxisZ import yokai.domain.ComposableAlertDialog import yokai.i18n.MR import yokai.presentation.settings.screen.about.AboutScreen @@ -40,11 +39,7 @@ class AboutController : BaseComposeController() { LocalBackPress provides router::handleBack, LocalRouter provides router, ) { - ScreenTransition( - navigator = it, - // FIXME: Mimic J2K's Conductor transition - transition = { materialSharedAxisZ(forward = it.lastEvent != StackEvent.Pop) }, - ) + CrossfadeTransition(navigator = it) } }, ) From 7ee9c7a7463d2763849f612e66f1b2bc429a11e9 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 29 Dec 2024 09:27:44 +0700 Subject: [PATCH 041/181] docs(i18n): Add README.md [skip ci] --- i18n/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 i18n/README.md diff --git a/i18n/README.md b/i18n/README.md new file mode 100644 index 0000000000..7975e9c17b --- /dev/null +++ b/i18n/README.md @@ -0,0 +1,5 @@ +# i18n + +This module houses the string resources and translations. + +Original English strings are managed in `src/commonMain/moko-resources/base/`. From f8807f81b1e72935260e0b039ef77073754af599 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 29 Dec 2024 09:48:00 +0700 Subject: [PATCH 042/181] chore: Remove unused translation strings [skip ci] --- i18n/src/commonMain/moko-resources/base/strings.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 8f00790c70..84d9b5cdab 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -7,10 +7,6 @@ More options Navigate up - File permissions required - TachiyomiJ2K requires access to all files in Android 11 to download chapters, create automatic backups, and read local series. \n\nOn the next screen, enable \"Allow access to manage all files.\" - TachiyomiJ2K requires access to all files to download chapters. Tap here, then enable \"Allow access to manage all files.\" - Welcome! Let\'s pick some defaults. You can always change these things later in the settings. Get started From dd6a2f377a3ae16e78898b0d240d436d9f278c95 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 29 Dec 2024 09:58:50 +0700 Subject: [PATCH 043/181] docs: Sync changelog [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f48dcf25f..ed22847141 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Refactor Reader ChapterTransition to use Compose (@arkon) - [Experimental] Add modified version of LargeTopAppBar that mimic J2K's ExpandedAppBarLayout - Refactor About page to use Compose +- Adjust Compose-based pages' transition to match J2K's Conductor transition ## [1.9.7] From 2f2ccac8e73c3677cf99de356740d990d6a81ff8 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 29 Dec 2024 10:08:20 +0700 Subject: [PATCH 044/181] docs: Weblate [skip ci] --- i18n/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/README.md b/i18n/README.md index 7975e9c17b..ae5b94078b 100644 --- a/i18n/README.md +++ b/i18n/README.md @@ -2,4 +2,4 @@ This module houses the string resources and translations. -Original English strings are managed in `src/commonMain/moko-resources/base/`. +Original English strings are managed in `src/commonMain/moko-resources/base/`. Translations are done externally via [Weblate](https://hosted.weblate.org/projects/yokai/). From a7874f2f29242307e2393edafd59eeb98bcf9153 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 29 Dec 2024 10:36:09 +0700 Subject: [PATCH 045/181] docs: Update README [skip ci] --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 33349a0c9b..2dc5958284 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,12 @@ A free and open source manga reader -[![CI](https://github.com/null2264/yokai/actions/workflows/build_push.yml/badge.svg)](https://github.com/null2264/yokai/actions/workflows/build_push.yml) -[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](/LICENSE) [![Discord: Mihon](https://img.shields.io/discord/1195734228319617024.svg?label=&labelColor=6A7EC2&color=7389D8&logo=discord&logoColor=FFFFFF)](https://discord.gg/mihon) -[![Mirror: GitLab](https://img.shields.io/badge/mirror-GitLab-orange.svg)](https://gitlab.com/null2264/yokai) +[![Mirror: GitLab](https://img.shields.io/badge/mirror-GitLab-orange.svg?labelColor=27303D)](https://gitlab.com/null2264/yokai) + +[![CI](https://github.com/null2264/yokai/actions/workflows/build_push.yml/badge.svg?labelColor=27303D)](https://github.com/null2264/yokai/actions/workflows/build_push.yml) +[![License: Apache-2.0](https://img.shields.io/github/license/null2264/yokai?labelColor=27303D&color=0877d2)](/LICENSE) +[![Translation status](https://img.shields.io/weblate/progress/yokai?labelColor=27303D&color=946300)](https://hosted.weblate.org/engage/yokai/) Yokai screenshots From f7e5abba59c1acf36cf7421633a1a4cc07472dd3 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 29 Dec 2024 10:40:09 +0700 Subject: [PATCH 046/181] ci: Ignore i18n changes except for base strings --- .github/workflows/build_check.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_check.yml b/.github/workflows/build_check.yml index abb7c971a7..d6b399c6e4 100644 --- a/.github/workflows/build_check.yml +++ b/.github/workflows/build_check.yml @@ -4,7 +4,15 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -on: [pull_request] +on: + pull_request: + paths: + - '**' + - '!**.md' + - '!i18n/src/commonMain/moko-resources/**/strings.xml' + - '!i18n/src/commonMain/moko-resources/**/plurals.xml' + - 'i18n/src/commonMain/moko-resources/base/strings.xml' + - 'i18n/src/commonMain/moko-resources/base/plurals.xml' jobs: build: From e554513392e4ed13c19760f3bb943ca76f85e4d0 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 30 Dec 2024 20:05:28 +0700 Subject: [PATCH 047/181] style: Scrollbar And add fast scroller component for later --- .../settings/SettingsCommonWidget.kt | 3 +- .../java/yokai/presentation/core/Scrollbar.kt | 249 ++++++++++ .../core/components/VerticalFastScroller.kt | 448 ++++++++++++++++++ 3 files changed, 699 insertions(+), 1 deletion(-) create mode 100644 presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt create mode 100644 presentation/core/src/main/java/yokai/presentation/core/components/VerticalFastScroller.kt diff --git a/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt b/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt index 7fc0fdb185..d4a02c21ac 100644 --- a/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt +++ b/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt @@ -29,6 +29,7 @@ import yokai.presentation.component.Gap import yokai.presentation.component.preference.Preference import yokai.presentation.component.preference.PreferenceItem import yokai.presentation.component.preference.widget.PreferenceGroupHeader +import yokai.presentation.core.drawVerticalScrollbar import yokai.presentation.core.enterAlwaysCollapsedScrollBehavior @Composable @@ -106,7 +107,7 @@ fun PreferenceScreen( } LazyColumn( - modifier = modifier, + modifier = modifier.drawVerticalScrollbar(listState), contentPadding = contentPadding, state = listState ) { diff --git a/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt b/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt new file mode 100644 index 0000000000..89bbc112be --- /dev/null +++ b/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt @@ -0,0 +1,249 @@ +package yokai.presentation.core + +import android.view.ViewConfiguration +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastFirstOrNull +import androidx.compose.ui.util.fastSumBy +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.sample +import yokai.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX + +/** + * Draws horizontal scrollbar to a LazyList. + * + * Set key with [STICKY_HEADER_KEY_PREFIX] prefix to any sticky header item in the list. + */ +fun Modifier.drawHorizontalScrollbar( + state: LazyListState, + reverseScrolling: Boolean = false, + // The amount of offset the scrollbar position towards the top of the layout + positionOffsetPx: Float = 0f, +): Modifier = drawScrollbar(state, Orientation.Horizontal, reverseScrolling, positionOffsetPx) + +/** + * Draws vertical scrollbar to a LazyList. + * + * Set key with [STICKY_HEADER_KEY_PREFIX] prefix to any sticky header item in the list. + */ +fun Modifier.drawVerticalScrollbar( + state: LazyListState, + reverseScrolling: Boolean = false, + // The amount of offset the scrollbar position towards the start of the layout + positionOffsetPx: Float = 0f, +): Modifier = drawScrollbar(state, Orientation.Vertical, reverseScrolling, positionOffsetPx) + +private fun Modifier.drawScrollbar( + state: LazyListState, + orientation: Orientation, + reverseScrolling: Boolean, + positionOffset: Float, +): Modifier = drawScrollbar( + orientation, + reverseScrolling, +) { reverseDirection, atEnd, thickness, color, alpha -> + val layoutInfo = state.layoutInfo + val viewportSize = if (orientation == Orientation.Horizontal) { + layoutInfo.viewportSize.width + } else { + layoutInfo.viewportSize.height + } - layoutInfo.beforeContentPadding - layoutInfo.afterContentPadding + val items = layoutInfo.visibleItemsInfo + val itemsSize = items.fastSumBy { it.size } + val showScrollbar = items.size < layoutInfo.totalItemsCount || itemsSize > viewportSize + val estimatedItemSize = if (items.isEmpty()) 0f else itemsSize.toFloat() / items.size + val totalSize = estimatedItemSize * layoutInfo.totalItemsCount + val thumbSize = viewportSize / totalSize * viewportSize + val startOffset = if (items.isEmpty()) { + 0f + } else { + items + .fastFirstOrNull { (it.key as? String)?.startsWith(STICKY_HEADER_KEY_PREFIX)?.not() ?: true } + ?.run { + val startPadding = if (reverseDirection) { + layoutInfo.afterContentPadding + } else { + layoutInfo.beforeContentPadding + } + startPadding + ((estimatedItemSize * index - offset) / totalSize * viewportSize) + } ?: 0f + } + val drawScrollbar = onDrawScrollbar( + orientation, reverseDirection, atEnd, showScrollbar, + thickness, color, alpha, thumbSize, startOffset, positionOffset, + ) + drawContent() + drawScrollbar() +} + +private fun ContentDrawScope.onDrawScrollbar( + orientation: Orientation, + reverseDirection: Boolean, + atEnd: Boolean, + showScrollbar: Boolean, + thickness: Float, + color: Color, + alpha: () -> Float, + thumbSize: Float, + scrollOffset: Float, + positionOffset: Float, +): DrawScope.() -> Unit { + val topLeft = if (orientation == Orientation.Horizontal) { + Offset( + if (reverseDirection) size.width - scrollOffset - thumbSize else scrollOffset, + if (atEnd) size.height - positionOffset - thickness else positionOffset, + ) + } else { + Offset( + if (atEnd) size.width - positionOffset - thickness else positionOffset, + if (reverseDirection) size.height - scrollOffset - thumbSize else scrollOffset, + ) + } + val size = if (orientation == Orientation.Horizontal) { + Size(thumbSize, thickness) + } else { + Size(thickness, thumbSize) + } + + return { + if (showScrollbar) { + drawRect( + color = color, + topLeft = topLeft, + size = size, + alpha = alpha(), + ) + } + } +} + +private fun Modifier.drawScrollbar( + orientation: Orientation, + reverseScrolling: Boolean, + onDraw: ContentDrawScope.( + reverseDirection: Boolean, + atEnd: Boolean, + thickness: Float, + color: Color, + alpha: () -> Float, + ) -> Unit, +): Modifier = composed { + val scrolled = remember { + MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST, + ) + } + val nestedScrollConnection = remember(orientation, scrolled) { + object : NestedScrollConnection { + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource, + ): Offset { + val delta = if (orientation == Orientation.Horizontal) consumed.x else consumed.y + if (delta != 0f) scrolled.tryEmit(Unit) + return Offset.Zero + } + } + } + + val alpha = remember { Animatable(0f) } + LaunchedEffect(scrolled, alpha) { + scrolled + .sample(100) + .collectLatest { + alpha.snapTo(1f) + alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec) + } + } + + val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr + val reverseDirection = if (orientation == Orientation.Horizontal) { + if (isLtr) reverseScrolling else !reverseScrolling + } else { + reverseScrolling + } + val atEnd = if (orientation == Orientation.Vertical) isLtr else true + + val context = LocalContext.current + val thickness = remember { ViewConfiguration.get(context).scaledScrollBarSize.toFloat() } + val color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.364f) + Modifier + .nestedScroll(nestedScrollConnection) + .drawWithContent { + onDraw(reverseDirection, atEnd, thickness, color, alpha::value) + } +} + +private val FadeOutAnimationSpec = tween( + durationMillis = ViewConfiguration.getScrollBarFadeDuration(), + delayMillis = ViewConfiguration.getScrollDefaultDelay(), +) + +@Preview(widthDp = 400, heightDp = 400, showBackground = true) +@Composable +fun LazyListScrollbarPreview() { + val state = rememberLazyListState() + LazyColumn( + modifier = Modifier.drawVerticalScrollbar(state), + state = state, + ) { + items(50) { + Text( + text = "Item ${it + 1}", + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) + } + } +} + +@Preview(widthDp = 400, showBackground = true) +@Composable +fun LazyListHorizontalScrollbarPreview() { + val state = rememberLazyListState() + LazyRow( + modifier = Modifier.drawHorizontalScrollbar(state), + state = state, + ) { + items(50) { + Text( + text = (it + 1).toString(), + modifier = Modifier + .padding(horizontal = 8.dp, vertical = 16.dp), + ) + } + } +} diff --git a/presentation/core/src/main/java/yokai/presentation/core/components/VerticalFastScroller.kt b/presentation/core/src/main/java/yokai/presentation/core/components/VerticalFastScroller.kt new file mode 100644 index 0000000000..27221e5e64 --- /dev/null +++ b/presentation/core/src/main/java/yokai/presentation/core/components/VerticalFastScroller.kt @@ -0,0 +1,448 @@ +package yokai.presentation.core.components + +import android.view.ViewConfiguration +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsDraggedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyListItemInfo +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.systemGestureExclusion +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.SubcomposeLayout +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastFirstOrNull +import androidx.compose.ui.util.fastForEach +import androidx.compose.ui.util.fastMaxBy +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.roundToInt +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.sample +import yokai.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX + +/** + * Draws vertical fast scroller to a lazy list + * + * Set key with [STICKY_HEADER_KEY_PREFIX] prefix to any sticky header item in the list. + */ +@Composable +fun VerticalFastScroller( + listState: LazyListState, + modifier: Modifier = Modifier, + thumbAllowed: () -> Boolean = { true }, + thumbColor: Color = MaterialTheme.colorScheme.primary, + topContentPadding: Dp = Dp.Hairline, + bottomContentPadding: Dp = Dp.Hairline, + endContentPadding: Dp = Dp.Hairline, + content: @Composable () -> Unit, +) { + SubcomposeLayout(modifier = modifier) { constraints -> + val contentPlaceable = subcompose("content", content).map { it.measure(constraints) } + val contentHeight = contentPlaceable.fastMaxBy { it.height }?.height ?: 0 + val contentWidth = contentPlaceable.fastMaxBy { it.width }?.width ?: 0 + + val scrollerConstraints = constraints.copy(minWidth = 0, minHeight = 0) + val scrollerPlaceable = subcompose("scroller") { + val layoutInfo = listState.layoutInfo + val showScroller = layoutInfo.visibleItemsInfo.size < layoutInfo.totalItemsCount + if (!showScroller) return@subcompose + + val thumbTopPadding = with(LocalDensity.current) { topContentPadding.toPx() } + var thumbOffsetY by remember(thumbTopPadding) { mutableFloatStateOf(thumbTopPadding) } + + val dragInteractionSource = remember { MutableInteractionSource() } + val isThumbDragged by dragInteractionSource.collectIsDraggedAsState() + val scrolled = remember { + MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST, + ) + } + + val thumbBottomPadding = with(LocalDensity.current) { bottomContentPadding.toPx() } + val heightPx = contentHeight.toFloat() - + thumbTopPadding - + thumbBottomPadding - + listState.layoutInfo.afterContentPadding + val thumbHeightPx = with(LocalDensity.current) { ThumbLength.toPx() } + val trackHeightPx = heightPx - thumbHeightPx + + // When thumb dragged + LaunchedEffect(thumbOffsetY) { + if (layoutInfo.totalItemsCount == 0 || !isThumbDragged) return@LaunchedEffect + val scrollRatio = (thumbOffsetY - thumbTopPadding) / trackHeightPx + val scrollItem = layoutInfo.totalItemsCount * scrollRatio + val scrollItemRounded = scrollItem.roundToInt() + val scrollItemSize = layoutInfo.visibleItemsInfo.find { it.index == scrollItemRounded }?.size ?: 0 + val scrollItemOffset = scrollItemSize * (scrollItem - scrollItemRounded) + listState.scrollToItem(index = scrollItemRounded, scrollOffset = scrollItemOffset.roundToInt()) + scrolled.tryEmit(Unit) + } + + // When list scrolled + LaunchedEffect(listState.firstVisibleItemScrollOffset) { + if (listState.layoutInfo.totalItemsCount == 0 || isThumbDragged) return@LaunchedEffect + val scrollOffset = computeScrollOffset(state = listState) + val scrollRange = computeScrollRange(state = listState) + val proportion = scrollOffset.toFloat() / (scrollRange.toFloat() - heightPx) + thumbOffsetY = trackHeightPx * proportion + thumbTopPadding + scrolled.tryEmit(Unit) + } + + // Thumb alpha + val alpha = remember { Animatable(0f) } + val isThumbVisible = alpha.value > 0f + LaunchedEffect(scrolled, alpha) { + scrolled + .sample(100) + .collectLatest { + if (thumbAllowed()) { + alpha.snapTo(1f) + alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec) + } else { + alpha.animateTo(0f, animationSpec = ImmediateFadeOutAnimationSpec) + } + } + } + + Box( + modifier = Modifier + .offset { IntOffset(0, thumbOffsetY.roundToInt()) } + .then( + // Recompose opts + if (isThumbVisible && !listState.isScrollInProgress) { + Modifier.draggable( + interactionSource = dragInteractionSource, + orientation = Orientation.Vertical, + state = rememberDraggableState { delta -> + val newOffsetY = thumbOffsetY + delta + thumbOffsetY = newOffsetY.coerceIn( + thumbTopPadding, + thumbTopPadding + trackHeightPx, + ) + }, + ) + } else { + Modifier + }, + ) + .then( + // Exclude thumb from gesture area only when needed + if (isThumbVisible && !isThumbDragged && !listState.isScrollInProgress) { + Modifier.systemGestureExclusion() + } else { + Modifier + }, + ) + .height(ThumbLength) + .padding(horizontal = 8.dp) + .padding(end = endContentPadding) + .width(ThumbThickness) + .alpha(alpha.value) + .background(color = thumbColor, shape = ThumbShape), + ) + }.map { it.measure(scrollerConstraints) } + val scrollerWidth = scrollerPlaceable.fastMaxBy { it.width }?.width ?: 0 + + layout(contentWidth, contentHeight) { + contentPlaceable.fastForEach { + it.place(0, 0) + } + scrollerPlaceable.fastForEach { + it.placeRelative(contentWidth - scrollerWidth, 0) + } + } + } +} + +@Composable +private fun rememberColumnWidthSums( + columns: GridCells, + horizontalArrangement: Arrangement.Horizontal, + contentPadding: PaddingValues, +) = remember List>( + columns, + horizontalArrangement, + contentPadding, +) { + { constraints -> + require(constraints.maxWidth != Constraints.Infinity) { + "LazyVerticalGrid's width should be bound by parent" + } + val horizontalPadding = contentPadding.calculateStartPadding(LayoutDirection.Ltr) + + contentPadding.calculateEndPadding(LayoutDirection.Ltr) + val gridWidth = constraints.maxWidth - horizontalPadding.roundToPx() + with(columns) { + calculateCrossAxisCellSizes( + gridWidth, + horizontalArrangement.spacing.roundToPx(), + ).toMutableList().apply { + for (i in 1.. Boolean = { true }, + thumbColor: Color = MaterialTheme.colorScheme.primary, + topContentPadding: Dp = Dp.Hairline, + bottomContentPadding: Dp = Dp.Hairline, + endContentPadding: Dp = Dp.Hairline, + content: @Composable () -> Unit, +) { + val slotSizesSums = rememberColumnWidthSums( + columns = columns, + horizontalArrangement = arrangement, + contentPadding = contentPadding, + ) + + SubcomposeLayout(modifier = modifier) { constraints -> + val contentPlaceable = subcompose("content", content).map { it.measure(constraints) } + val contentHeight = contentPlaceable.fastMaxBy { it.height }?.height ?: 0 + val contentWidth = contentPlaceable.fastMaxBy { it.width }?.width ?: 0 + + val scrollerConstraints = constraints.copy(minWidth = 0, minHeight = 0) + val scrollerPlaceable = subcompose("scroller") { + val layoutInfo = state.layoutInfo + val showScroller = layoutInfo.visibleItemsInfo.size < layoutInfo.totalItemsCount + if (!showScroller) return@subcompose + val thumbTopPadding = with(LocalDensity.current) { topContentPadding.toPx() } + var thumbOffsetY by remember(thumbTopPadding) { mutableFloatStateOf(thumbTopPadding) } + + val dragInteractionSource = remember { MutableInteractionSource() } + val isThumbDragged by dragInteractionSource.collectIsDraggedAsState() + val scrolled = remember { + MutableSharedFlow( + extraBufferCapacity = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST, + ) + } + + val thumbBottomPadding = with(LocalDensity.current) { bottomContentPadding.toPx() } + val heightPx = contentHeight.toFloat() - + thumbTopPadding - + thumbBottomPadding - + state.layoutInfo.afterContentPadding + val thumbHeightPx = with(LocalDensity.current) { ThumbLength.toPx() } + val trackHeightPx = heightPx - thumbHeightPx + + val columnCount = remember { slotSizesSums(constraints).size } + + // When thumb dragged + LaunchedEffect(thumbOffsetY) { + if (layoutInfo.totalItemsCount == 0 || !isThumbDragged) return@LaunchedEffect + val scrollRatio = (thumbOffsetY - thumbTopPadding) / trackHeightPx + val scrollItem = layoutInfo.totalItemsCount * scrollRatio + // I can't think of anything else rn but this'll do + val scrollItemWhole = scrollItem.toInt() + val columnNum = ((scrollItemWhole + 1) % columnCount).takeIf { it != 0 } ?: columnCount + val scrollItemFraction = if (scrollItemWhole == 0) scrollItem else scrollItem % scrollItemWhole + val offsetPerItem = 1f / columnCount + val offsetRatio = (offsetPerItem * scrollItemFraction) + (offsetPerItem * (columnNum - 1)) + + // TODO: Sometimes item height is not available when scrolling up + val scrollItemSize = (1..columnCount).maxOf { num -> + val actualIndex = if (num != columnNum) { + scrollItemWhole + num - columnCount + } else { + scrollItemWhole + } + layoutInfo.visibleItemsInfo.find { it.index == actualIndex }?.size?.height ?: 0 + } + val scrollItemOffset = scrollItemSize * offsetRatio + + state.scrollToItem(index = scrollItemWhole, scrollOffset = scrollItemOffset.roundToInt()) + scrolled.tryEmit(Unit) + } + + // When list scrolled + LaunchedEffect(state.firstVisibleItemScrollOffset) { + if (state.layoutInfo.totalItemsCount == 0 || isThumbDragged) return@LaunchedEffect + val scrollOffset = computeScrollOffset(state = state) + val scrollRange = computeScrollRange(state = state) + val proportion = scrollOffset.toFloat() / (scrollRange.toFloat() - heightPx) + thumbOffsetY = trackHeightPx * proportion + thumbTopPadding + scrolled.tryEmit(Unit) + } + + // Thumb alpha + val alpha = remember { Animatable(0f) } + val isThumbVisible = alpha.value > 0f + LaunchedEffect(scrolled, alpha) { + scrolled + .sample(100) + .collectLatest { + if (thumbAllowed()) { + alpha.snapTo(1f) + alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec) + } else { + alpha.animateTo(0f, animationSpec = ImmediateFadeOutAnimationSpec) + } + } + } + + Box( + modifier = Modifier + .offset { IntOffset(0, thumbOffsetY.roundToInt()) } + .then( + // Recompose opts + if (isThumbVisible && !state.isScrollInProgress) { + Modifier.draggable( + interactionSource = dragInteractionSource, + orientation = Orientation.Vertical, + state = rememberDraggableState { delta -> + val newOffsetY = thumbOffsetY + delta + thumbOffsetY = newOffsetY.coerceIn( + thumbTopPadding, + thumbTopPadding + trackHeightPx, + ) + }, + ) + } else { + Modifier + }, + ) + .then( + // Exclude thumb from gesture area only when needed + if (isThumbVisible && !isThumbDragged && !state.isScrollInProgress) { + Modifier.systemGestureExclusion() + } else { + Modifier + }, + ) + .height(ThumbLength) + .padding(end = endContentPadding) + .width(ThumbThickness) + .alpha(alpha.value) + .background(color = thumbColor, shape = ThumbShape), + ) + }.map { it.measure(scrollerConstraints) } + val scrollerWidth = scrollerPlaceable.fastMaxBy { it.width }?.width ?: 0 + + layout(contentWidth, contentHeight) { + contentPlaceable.fastForEach { + it.place(0, 0) + } + scrollerPlaceable.fastForEach { + it.placeRelative(contentWidth - scrollerWidth, 0) + } + } + } +} + +private fun computeScrollOffset(state: LazyGridState): Int { + if (state.layoutInfo.totalItemsCount == 0) return 0 + val visibleItems = state.layoutInfo.visibleItemsInfo + val startChild = visibleItems.first() + val endChild = visibleItems.last() + val minPosition = min(startChild.index, endChild.index) + val maxPosition = max(startChild.index, endChild.index) + val itemsBefore = minPosition.coerceAtLeast(0) + val startDecoratedTop = startChild.offset.y + val laidOutArea = abs((endChild.offset.y + endChild.size.height) - startDecoratedTop) + val itemRange = abs(minPosition - maxPosition) + 1 + val avgSizePerRow = laidOutArea.toFloat() / itemRange + return (itemsBefore * avgSizePerRow + (0 - startDecoratedTop)).roundToInt() +} + +private fun computeScrollRange(state: LazyGridState): Int { + if (state.layoutInfo.totalItemsCount == 0) return 0 + val visibleItems = state.layoutInfo.visibleItemsInfo + val startChild = visibleItems.first() + val endChild = visibleItems.last() + val laidOutArea = (endChild.offset.y + endChild.size.height) - startChild.offset.y + val laidOutRange = abs(startChild.index - endChild.index) + 1 + return (laidOutArea.toFloat() / laidOutRange * state.layoutInfo.totalItemsCount).roundToInt() +} + +private fun computeScrollOffset(state: LazyListState): Int { + if (state.layoutInfo.totalItemsCount == 0) return 0 + val visibleItems = state.layoutInfo.visibleItemsInfo + val startChild = visibleItems + .fastFirstOrNull { (it.key as? String)?.startsWith(STICKY_HEADER_KEY_PREFIX)?.not() ?: true }!! + val endChild = visibleItems.last() + val minPosition = min(startChild.index, endChild.index) + val maxPosition = max(startChild.index, endChild.index) + val itemsBefore = minPosition.coerceAtLeast(0) + val startDecoratedTop = startChild.top + val laidOutArea = abs(endChild.bottom - startDecoratedTop) + val itemRange = abs(minPosition - maxPosition) + 1 + val avgSizePerRow = laidOutArea.toFloat() / itemRange + return (itemsBefore * avgSizePerRow + (0 - startDecoratedTop)).roundToInt() +} + +private fun computeScrollRange(state: LazyListState): Int { + if (state.layoutInfo.totalItemsCount == 0) return 0 + val visibleItems = state.layoutInfo.visibleItemsInfo + val startChild = visibleItems + .fastFirstOrNull { (it.key as? String)?.startsWith(STICKY_HEADER_KEY_PREFIX)?.not() ?: true }!! + val endChild = visibleItems.last() + val laidOutArea = endChild.bottom - startChild.top + val laidOutRange = abs(startChild.index - endChild.index) + 1 + return (laidOutArea.toFloat() / laidOutRange * state.layoutInfo.totalItemsCount).roundToInt() +} + +object Scroller { + const val STICKY_HEADER_KEY_PREFIX = "sticky:" +} + +private val ThumbLength = 48.dp +private val ThumbThickness = 12.dp +private val ThumbShape = RoundedCornerShape(ThumbThickness / 2) +private val FadeOutAnimationSpec = tween( + durationMillis = ViewConfiguration.getScrollBarFadeDuration(), + delayMillis = 2000, +) +private val ImmediateFadeOutAnimationSpec = tween( + durationMillis = ViewConfiguration.getScrollBarFadeDuration(), +) + +private val LazyListItemInfo.top: Int + get() = offset + +private val LazyListItemInfo.bottom: Int + get() = offset + size From 67477956904906638938a3bcb9716f4b686a5851 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 30 Dec 2024 20:28:44 +0700 Subject: [PATCH 048/181] refactor: Use fast*() util functions --- .../core/src/main/java/yokai/presentation/core/AppBar.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt index 0d6cb21141..35932b5fbd 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -55,6 +55,7 @@ import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.isFinite import androidx.compose.ui.unit.isSpecified +import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.fastFirst import kotlin.math.abs import kotlin.math.max @@ -520,11 +521,11 @@ private fun TopAppBarState.rawTopHeightOffset(topHeightPx: Float, totalHeightPx: } private fun TopAppBarState.topHeightOffset(topHeightPx: Float, totalHeightPx: Float): Float { - return rawTopHeightOffset(topHeightPx, totalHeightPx).coerceIn(-topHeightPx, 0f) + return rawTopHeightOffset(topHeightPx, totalHeightPx).fastCoerceIn(-topHeightPx, 0f) } private fun TopAppBarState.bottomHeightOffset(topHeightPx: Float, totalHeightPx: Float): Float { - return heightOffset.coerceIn(topHeightPx - totalHeightPx, 0f) + return heightOffset.fastCoerceIn(topHeightPx - totalHeightPx, 0f) } private fun TopAppBarState.topCollapsedFraction(topHeightPx: Float, totalHeightPx: Float): Float { @@ -580,7 +581,7 @@ private class EnterAlwaysCollapsedScrollBehavior( heightOffset = if (isAtTop()) { offset } else { - offset.coerceIn(-totalHeightPx, (topHeightPx - totalHeightPx)) + offset.fastCoerceIn(-totalHeightPx, (topHeightPx - totalHeightPx)) } } From 3399d6a326495b8a1e14a372223a7f64b993d19c Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 30 Dec 2024 20:30:49 +0700 Subject: [PATCH 049/181] docs: Remove `Translation` section [skip ci] Handled by Weblate --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed22847141..6561794ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,6 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - `Additions` - New features - `Changes` - Behaviour/visual changes - `Fixes` - Bugfixes -- `Translation` - Translation changes/updates - `Other` - Technical changes/updates ## [Unreleased] From 8a28d1d4845f0b31064e3085d3a6fd91421e6808 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 31 Dec 2024 12:24:45 +0700 Subject: [PATCH 050/181] fix: Emit scrolled event from onPreScroll --- .../src/main/java/yokai/presentation/core/Scrollbar.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt b/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt index 89bbc112be..84a462f2e1 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt @@ -166,12 +166,8 @@ private fun Modifier.drawScrollbar( } val nestedScrollConnection = remember(orientation, scrolled) { object : NestedScrollConnection { - override fun onPostScroll( - consumed: Offset, - available: Offset, - source: NestedScrollSource, - ): Offset { - val delta = if (orientation == Orientation.Horizontal) consumed.x else consumed.y + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + val delta = if (orientation == Orientation.Horizontal) available.x else available.y if (delta != 0f) scrolled.tryEmit(Unit) return Offset.Zero } From ac0d2e9fc09180bc3ed3eae1ab526f2cb833582d Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 31 Dec 2024 12:46:33 +0700 Subject: [PATCH 051/181] revert: "fix: Emit scrolled event from onPreScroll" --- .../src/main/java/yokai/presentation/core/Scrollbar.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt b/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt index 84a462f2e1..89bbc112be 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt @@ -166,8 +166,12 @@ private fun Modifier.drawScrollbar( } val nestedScrollConnection = remember(orientation, scrolled) { object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - val delta = if (orientation == Orientation.Horizontal) available.x else available.y + override fun onPostScroll( + consumed: Offset, + available: Offset, + source: NestedScrollSource, + ): Offset { + val delta = if (orientation == Orientation.Horizontal) consumed.x else consumed.y if (delta != 0f) scrolled.tryEmit(Unit) return Offset.Zero } From 1b92ae2e5f46ecb2679dfccfd2dd0880788afaca Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 31 Dec 2024 12:53:37 +0700 Subject: [PATCH 052/181] chore: License and FIXME note --- .../java/yokai/presentation/core/Scrollbar.kt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt b/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt index 89bbc112be..12e30420bc 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/Scrollbar.kt @@ -1,5 +1,36 @@ package yokai.presentation.core +/* + * MIT License + * + * Copyright (c) 2022 Albert Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * Code taken from https://gist.github.com/mxalbert1996/33a360fcab2105a31e5355af98216f5a + * with some modifications to handle contentPadding. + * + * Modifiers for regular scrollable list is omitted. + */ + import android.view.ViewConfiguration import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween @@ -39,6 +70,8 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.sample import yokai.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX +// FIXME: Scrollbar won't show up when TopAppBar is expanding/collapsing + /** * Draws horizontal scrollbar to a LazyList. * From d02f1bdd11514bc3920f6f6f10d1481593128465 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 1 Jan 2025 07:10:30 +0700 Subject: [PATCH 053/181] refactor: Don't use context receiver Deprecated on Kotlin 2.x, scheduled for removal in v2.1.x, will be replaced with context parameters REF: https://github.com/Kotlin/KEEP/issues/259#issuecomment-2278319746 REF: https://youtrack.jetbrains.com/issue/KT-67119/Migration-warning-from-context-receivers-to-context-parameters REF: https://github.com/Kotlin/KEEP/issues/367 --- app/build.gradle.kts | 1 - .../data/track/anilist/AnilistApi.kt | 52 ++++----- .../data/track/bangumi/BangumiApi.kt | 60 +++++----- .../tachiyomi/data/track/kavita/KavitaApi.kt | 12 +- .../tachiyomi/data/track/kitsu/KitsuApi.kt | 108 ++++++++---------- .../tachiyomi/data/track/komga/KomgaApi.kt | 25 ++-- .../track/mangaupdates/MangaUpdatesApi.kt | 74 ++++++------ .../data/track/myanimelist/MyAnimeListApi.kt | 100 +++++++--------- .../myanimelist/MyAnimeListInterceptor.kt | 2 +- .../data/track/shikimori/ShikimoriApi.kt | 53 ++++----- .../data/track/suwayomi/TachideskApi.kt | 8 +- .../data/updater/AppUpdateChecker.kt | 78 ++++++------- .../tachiyomi/extension/api/ExtensionApi.kt | 10 +- app/src/main/java/yokai/core/di/AppModule.kt | 4 + .../repo/service/ExtensionRepoService.kt | 14 +-- core/build.gradle.kts | 1 - .../tachiyomi/network/OkHttpExtensions.kt | 10 +- 17 files changed, 265 insertions(+), 347 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a0f66ecfda..1c987b5cc1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -278,7 +278,6 @@ tasks { // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers) withType { compilerOptions.freeCompilerArgs.addAll( - "-Xcontext-receivers", // "-opt-in=kotlin.Experimental", "-opt-in=kotlin.RequiresOptIn", "-opt-in=kotlin.ExperimentalStdlibApi", diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index d86b58ac72..69e554f1aa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -49,15 +49,13 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { put("completedAt", createDate(track.finished_reading_date)) } } - with(json) { - authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) - .awaitSuccess() - .parseAs() - .let { - track.library_id = it.data.entry.id - track - } - } + authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) + .awaitSuccess() + .parseAs() + .let { + track.library_id = it.data.entry.id + track + } } } @@ -74,11 +72,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { put("completedAt", createDate(track.finished_reading_date)) } } - with(json) { - authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) - .awaitSuccess() - track - } + authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) + .awaitSuccess() + track } } @@ -90,13 +86,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { put("query", search) } } - with(json) { - authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) - .awaitSuccess() - .parseAs() - .data.page.media - .map { it.toALManga().toTrack() } - } + authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) + .awaitSuccess() + .parseAs() + .data.page.media + .map { it.toALManga().toTrack() } } } @@ -109,15 +103,13 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { put("manga_id", track.media_id) } } - with(json) { - authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) - .awaitSuccess() - .parseAs() - .data.page.mediaList - .map { it.toALUserManga() } - .firstOrNull() - ?.toTrack() - } + authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) + .awaitSuccess() + .parseAs() + .data.page.mediaList + .map { it.toALUserManga() } + .firstOrNull() + ?.toTrack() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt index 77c6694a20..44fc5c9d5d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt @@ -75,29 +75,25 @@ class BangumiApi( .appendQueryParameter("responseGroup", "large") .appendQueryParameter("max_results", "20") .build() - with(json) { - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .let { result -> - if (result.code == 404) emptyList() + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .let { result -> + if (result.code == 404) emptyList() - result.list - ?.map { it.toTrackSearch(trackId) } - .orEmpty() - } - } + result.list + ?.map { it.toTrackSearch(trackId) } + .orEmpty() + } } } suspend fun findLibManga(track: Track): Track? { return withIOContext { - with(json) { - authClient.newCall(GET("$API_URL/subject/${track.media_id}")) - .awaitSuccess() - .parseAs() - .toTrackSearch(trackId) - } + authClient.newCall(GET("$API_URL/subject/${track.media_id}")) + .awaitSuccess() + .parseAs() + .toTrackSearch(trackId) } } @@ -111,29 +107,25 @@ class BangumiApi( .build() // TODO: get user readed chapter here - with(json) { - authClient.newCall(requestUserRead) - .awaitSuccess() - .parseAs() - .let { - if (it.code == 400) return@let null + authClient.newCall(requestUserRead) + .awaitSuccess() + .parseAs() + .let { + if (it.code == 400) return@let null - track.status = it.status?.id?.toInt() ?: Bangumi.DEFAULT_STATUS - track.last_chapter_read = it.epStatus!!.toFloat() - track.score = it.rating!!.toFloat() - track - } - } + track.status = it.status?.id?.toInt() ?: Bangumi.DEFAULT_STATUS + track.last_chapter_read = it.epStatus!!.toFloat() + track.score = it.rating!!.toFloat() + track + } } } suspend fun accessToken(code: String): BGMOAuth { return withIOContext { - with(json) { - client.newCall(accessTokenRequest(code)) - .awaitSuccess() - .parseAs() - } + client.newCall(accessTokenRequest(code)) + .awaitSuccess() + .parseAs() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt index 2157c1b69d..f737edc924 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt @@ -44,7 +44,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor try { client.newCall(request).execute().use { when (it.code) { - 200 -> return with(json) { it.parseAs().token } + 200 -> return it.parseAs().token 401 -> { Logger.w { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" } throw IOException("Unauthorized / api key not valid") @@ -89,11 +89,10 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor private fun getTotalChapters(url: String): Long { val requestUrl = getApiVolumesUrl(url) try { - val listVolumeDto = with(json) { + val listVolumeDto = authClient.newCall(GET(requestUrl)) .execute() .parseAs>() - } var volumeNumber = 0L var maxChapterNumber = 0L for (volume in listVolumeDto) { @@ -117,9 +116,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor try { authClient.newCall(GET(requestUrl)).execute().use { if (it.code == 200) { - return with(json) { - it.parseAs().number!!.replace(",", ".").toFloat() - } + return it.parseAs().number!!.replace(",", ".").toFloat() } if (it.code == 204) { return 0F @@ -134,11 +131,10 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor suspend fun getTrackSearch(url: String): TrackSearch = withIOContext { try { - val serieDto: SeriesDto = with(json) { + val serieDto: SeriesDto = authClient.newCall(GET(url)) .awaitSuccess() .parseAs() - } val track = serieDto.toTrack() track.apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt index 288d9e5c18..b01a0a3a7c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt @@ -131,14 +131,12 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) suspend fun search(query: String): List { return withIOContext { - with(json) { - authClient.newCall(GET(ALGOLIA_KEY_URL)) - .awaitSuccess() - .parseAs() - .let { - algoliaSearch(it.media.key, query) - } - } + authClient.newCall(GET(ALGOLIA_KEY_URL)) + .awaitSuccess() + .parseAs() + .let { + algoliaSearch(it.media.key, query) + } } } @@ -147,25 +145,23 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) val jsonObject = buildJsonObject { put("params", "query=$query$ALGOLIA_FILTER") } - with(json) { - client.newCall( - POST( - ALGOLIA_URL, - headers = headersOf( - "X-Algolia-Application-Id", - ALGOLIA_APP_ID, - "X-Algolia-API-Key", - key, - ), - body = jsonObject.toString().toRequestBody(jsonMime), + client.newCall( + POST( + ALGOLIA_URL, + headers = headersOf( + "X-Algolia-Application-Id", + ALGOLIA_APP_ID, + "X-Algolia-API-Key", + key, ), - ) - .awaitSuccess() - .parseAs() - .hits - .filter { it.subtype != "novel" } - .map { it.toTrack() } - } + body = jsonObject.toString().toRequestBody(jsonMime), + ), + ) + .awaitSuccess() + .parseAs() + .hits + .filter { it.subtype != "novel" } + .map { it.toTrack() } } } @@ -175,18 +171,16 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) .encodedQuery("filter[manga_id]=${track.media_id}&filter[user_id]=$userId") .appendQueryParameter("include", "manga") .build() - with(json) { - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .let { - if (it.data.isNotEmpty() && it.included.isNotEmpty()) { - it.firstToTrack() - } else { - null - } + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .let { + if (it.data.isNotEmpty() && it.included.isNotEmpty()) { + it.firstToTrack() + } else { + null } - } + } } } @@ -196,18 +190,16 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) .encodedQuery("filter[id]=${track.media_id}") .appendQueryParameter("include", "manga") .build() - with(json) { - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .let { - if (it.data.isNotEmpty() && it.included.isNotEmpty()) { - it.firstToTrack() - } else { - throw Exception("Could not find manga") - } + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .let { + if (it.data.isNotEmpty() && it.included.isNotEmpty()) { + it.firstToTrack() + } else { + throw Exception("Could not find manga") } - } + } } } @@ -220,11 +212,9 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) .add("client_id", CLIENT_ID) .add("client_secret", CLIENT_SECRET) .build() - with(json) { - client.newCall(POST(LOGIN_URL, body = formBody)) - .awaitSuccess() - .parseAs() - } + client.newCall(POST(LOGIN_URL, body = formBody)) + .awaitSuccess() + .parseAs() } } @@ -233,13 +223,11 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) val url = "${BASE_URL}users".toUri().buildUpon() .encodedQuery("filter[self]=true") .build() - with(json) { - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .data[0] - .id - } + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .data[0] + .id } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt index 68f3bed857..ab2ffd327c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt @@ -26,21 +26,19 @@ class KomgaApi(private val client: OkHttpClient) { withIOContext { try { val track = - with(json) { - if (url.contains(READLIST_API)) { - client.newCall(GET(url)) - .awaitSuccess() - .parseAs() - .toTrack() - } else { - client.newCall(GET(url)) - .awaitSuccess() - .parseAs() - .toTrack() - } + if (url.contains(READLIST_API)) { + client.newCall(GET(url)) + .awaitSuccess() + .parseAs() + .toTrack() + } else { + client.newCall(GET(url)) + .awaitSuccess() + .parseAs() + .toTrack() } - val progress = with(json) { + val progress = client .newCall( GET( @@ -59,7 +57,6 @@ class KomgaApi(private val client: OkHttpClient) { it.parseAs().toV2() } } - } track.apply { cover_url = "$url/thumbnail" tracking_url = url diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt index ab664dd504..a4b8887c83 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt @@ -37,15 +37,13 @@ class MangaUpdatesApi( suspend fun getSeriesListItem(track: Track): Pair { val listItem = - with(json) { - authClient.newCall( - GET( - url = "$BASE_URL/v1/lists/series/${track.media_id}", - ), - ) - .awaitSuccess() - .parseAs() - } + authClient.newCall( + GET( + url = "$BASE_URL/v1/lists/series/${track.media_id}", + ), + ) + .awaitSuccess() + .parseAs() val rating = getSeriesRating(track) @@ -104,15 +102,13 @@ class MangaUpdatesApi( private suspend fun getSeriesRating(track: Track): MURating? { return try { - with(json) { - authClient.newCall( - GET( - url = "$BASE_URL/v1/series/${track.media_id}/rating", - ), - ) - .awaitSuccess() - .parseAs() - } + authClient.newCall( + GET( + url = "$BASE_URL/v1/series/${track.media_id}/rating", + ), + ) + .awaitSuccess() + .parseAs() } catch (e: Exception) { null } @@ -151,18 +147,16 @@ class MangaUpdatesApi( }, ) } - return with(json) { - client.newCall( - POST( - url = "$BASE_URL/v1/series/search", - body = body.toString().toRequestBody(CONTENT_TYPE), - ), - ) - .awaitSuccess() - .parseAs() - .results - .map { it.record } - } + return client.newCall( + POST( + url = "$BASE_URL/v1/series/search", + body = body.toString().toRequestBody(CONTENT_TYPE), + ), + ) + .awaitSuccess() + .parseAs() + .results + .map { it.record } } suspend fun authenticate(username: String, password: String): MUContext? { @@ -170,17 +164,15 @@ class MangaUpdatesApi( put("username", username) put("password", password) } - return with(json) { - client.newCall( - PUT( - url = "$BASE_URL/v1/account/login", - body = body.toString().toRequestBody(CONTENT_TYPE), - ), - ) - .awaitSuccess() - .parseAs() - .context - } + return client.newCall( + PUT( + url = "$BASE_URL/v1/account/login", + body = body.toString().toRequestBody(CONTENT_TYPE), + ), + ) + .awaitSuccess() + .parseAs() + .context } companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt index 4a5bc58d0a..9468ee359e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt @@ -45,11 +45,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .add("code_verifier", codeVerifier) .add("grant_type", "authorization_code") .build() - with(json) { - client.newCall(POST("$BASE_OAUTH_URL/token", body = formBody)) - .awaitSuccess() - .parseAs() - } + client.newCall(POST("$BASE_OAUTH_URL/token", body = formBody)) + .awaitSuccess() + .parseAs() } } @@ -59,12 +57,10 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .url("$BASE_API_URL/users/@me") .get() .build() - with(json) { - authClient.newCall(request) - .awaitSuccess() - .parseAs() - .name - } + authClient.newCall(request) + .awaitSuccess() + .parseAs() + .name } } @@ -75,15 +71,13 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .appendQueryParameter("q", query.take(64)) .appendQueryParameter("nsfw", "true") .build() - with(json) { - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .data - .map { async { getMangaDetails(it.node.id) } } - .awaitAll() - .filter { !it.publishing_type.contains("novel") } - } + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .data + .map { async { getMangaDetails(it.node.id) } } + .awaitAll() + .filter { !it.publishing_type.contains("novel") } } } @@ -93,24 +87,22 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .appendPath(id.toString()) .appendQueryParameter("fields", "id,title,synopsis,num_chapters,main_picture,status,media_type,start_date") .build() - with(json) { - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .let { - TrackSearch.create(TrackManager.MYANIMELIST).apply { - media_id = it.id - title = it.title - summary = it.synopsis - total_chapters = it.numChapters - cover_url = it.covers.large - tracking_url = "https://myanimelist.net/manga/$media_id" - publishing_status = it.status.replace("_", " ") - publishing_type = it.mediaType.replace("_", " ") - start_date = it.startDate ?: "" - } + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .let { + TrackSearch.create(TrackManager.MYANIMELIST).apply { + media_id = it.id + title = it.title + summary = it.synopsis + total_chapters = it.numChapters + cover_url = it.covers.large + tracking_url = "https://myanimelist.net/manga/$media_id" + publishing_status = it.status.replace("_", " ") + publishing_type = it.mediaType.replace("_", " ") + start_date = it.startDate ?: "" } - } + } } } @@ -132,12 +124,10 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .url(mangaUrl(track.media_id).toString()) .put(formBodyBuilder.build()) .build() - with(json) { - authClient.newCall(request) - .awaitSuccess() - .parseAs() - .let { parseMangaItem(it, track) } - } + authClient.newCall(request) + .awaitSuccess() + .parseAs() + .let { parseMangaItem(it, track) } } } @@ -147,15 +137,13 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .appendPath(track.media_id.toString()) .appendQueryParameter("fields", "num_chapters,my_list_status{start_date,finish_date}") .build() - with(json) { - authClient.newCall(GET(uri.toString())) - .awaitSuccess() - .parseAs() - .let { item -> - track.total_chapters = item.numChapters - item.myListStatus?.let { parseMangaItem(it, track) } - } - } + authClient.newCall(GET(uri.toString())) + .awaitSuccess() + .parseAs() + .let { item -> + track.total_chapters = item.numChapters + item.myListStatus?.let { parseMangaItem(it, track) } + } } } @@ -190,11 +178,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI .url(urlBuilder.build().toString()) .get() .build() - with(json) { - authClient.newCall(request) - .awaitSuccess() - .parseAs() - } + authClient.newCall(request) + .awaitSuccess() + .parseAs() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt index 9b7de62837..f1fa96f48b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt @@ -66,7 +66,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor return runCatching { if (response.isSuccessful) { - with(json) { response.parseAs() } + response.parseAs() } else { response.close() null diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt index 456acbdfdd..61286976fa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt @@ -74,12 +74,10 @@ class ShikimoriApi( .appendQueryParameter("search", search) .appendQueryParameter("limit", "20") .build() - with(json) { - authClient.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs>() - .map { it.toTrack(trackId) } - } + authClient.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs>() + .map { it.toTrack(trackId) } } } @@ -102,51 +100,44 @@ class ShikimoriApi( val urlMangas = "$API_URL/mangas".toUri().buildUpon() .appendPath(track.media_id.toString()) .build() - val manga = with(json) { + val manga = authClient.newCall(GET(urlMangas.toString())) .awaitSuccess() .parseAs() - } val url = "$API_URL/v2/user_rates".toUri().buildUpon() .appendQueryParameter("user_id", user_id) .appendQueryParameter("target_id", track.media_id.toString()) .appendQueryParameter("target_type", "Manga") .build() - with(json) { - authClient.newCall(GET(url.toString())) - .execute() - .parseAs>() - .let { entries -> - if (entries.size > 1) { - throw Exception("Too manga manga in response") - } - entries - .map { it.toTrack(trackId, manga) } - .firstOrNull() + authClient.newCall(GET(url.toString())) + .execute() + .parseAs>() + .let { entries -> + if (entries.size > 1) { + throw Exception("Too manga manga in response") } - } + entries + .map { it.toTrack(trackId, manga) } + .firstOrNull() + } } } suspend fun getCurrentUser(): Int { return withIOContext { - with(json) { - authClient.newCall(GET("$API_URL/users/whoami")) - .awaitSuccess() - .parseAs() - .id - } + authClient.newCall(GET("$API_URL/users/whoami")) + .awaitSuccess() + .parseAs() + .id } } suspend fun accessToken(code: String): SMOAuth { return withIOContext { - with(json) { - client.newCall(accessTokenRequest(code)) - .awaitSuccess() - .parseAs() - } + client.newCall(accessTokenRequest(code)) + .awaitSuccess() + .parseAs() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt index 7c8f70f519..7d67193034 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt @@ -52,9 +52,7 @@ class TachideskApi { trackUrl } - val manga = with(json) { - client.newCall(GET("$url/full", headers)).awaitSuccess().parseAs() - } + val manga = client.newCall(GET("$url/full", headers)).awaitSuccess().parseAs() TrackSearch.create(TrackManager.SUWAYOMI).apply { title = manga.title @@ -74,9 +72,7 @@ class TachideskApi { suspend fun updateProgress(track: Track): Track { val url = track.tracking_url - val chapters = with(json) { - client.newCall(GET("$url/chapters", headers)).awaitSuccess().parseAs>() - } + val chapters = client.newCall(GET("$url/chapters", headers)).awaitSuccess().parseAs>() val lastChapterIndex = chapters.first { it.chapterNumber == track.last_chapter_read }.index client.newCall( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt index 1ee4855567..05d7755975 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt @@ -11,12 +11,12 @@ import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.util.system.localeContext import eu.kanade.tachiyomi.util.system.withIOContext +import java.util.Date +import java.util.concurrent.TimeUnit import kotlinx.serialization.json.Json import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import yokai.domain.base.models.Version -import java.util.* -import java.util.concurrent.* class AppUpdateChecker( private val json: Json = Injekt.get(), @@ -31,48 +31,46 @@ class AppUpdateChecker( } return withIOContext { - val result = with(json) { - if (preferences.checkForBetas().get()) { - networkService.client - .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases")) - .await() - .parseAs>() - .let { githubReleases -> - val releases = - githubReleases.take(10).filter { isNewVersion(it.version) } - // Check if any of the latest versions are newer than the current version - val release = releases - .maxWithOrNull { r1, r2 -> - when { - r1.version == r2.version -> 0 - isNewVersion(r2.version, r1.version) -> -1 - else -> 1 - } + val result = if (preferences.checkForBetas().get()) { + networkService.client + .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases")) + .await() + .parseAs>() + .let { githubReleases -> + val releases = + githubReleases.take(10).filter { isNewVersion(it.version) } + // Check if any of the latest versions are newer than the current version + val release = releases + .maxWithOrNull { r1, r2 -> + when { + r1.version == r2.version -> 0 + isNewVersion(r2.version, r1.version) -> -1 + else -> 1 } - preferences.lastAppCheck().set(Date().time) - - if (release != null) { - AppUpdateResult.NewUpdate(release) - } else { - AppUpdateResult.NoNewUpdate } - } - } else { - networkService.client - .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest")) - .await() - .parseAs() - .let { - preferences.lastAppCheck().set(Date().time) + preferences.lastAppCheck().set(Date().time) - // Check if latest version is newer than the current version - if (isNewVersion(it.version)) { - AppUpdateResult.NewUpdate(it) - } else { - AppUpdateResult.NoNewUpdate - } + if (release != null) { + AppUpdateResult.NewUpdate(release) + } else { + AppUpdateResult.NoNewUpdate } - } + } + } else { + networkService.client + .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest")) + .await() + .parseAs() + .let { + preferences.lastAppCheck().set(Date().time) + + // Check if latest version is newer than the current version + if (isNewVersion(it.version)) { + AppUpdateResult.NewUpdate(it) + } else { + AppUpdateResult.NoNewUpdate + } + } } if (doExtrasAfterNewUpdate && result is AppUpdateResult.NewUpdate) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt index a33e307fb6..0eaf7c2f80 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt @@ -14,7 +14,6 @@ import eu.kanade.tachiyomi.util.system.withIOContext import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy @@ -24,7 +23,6 @@ import yokai.domain.extension.repo.model.ExtensionRepo internal class ExtensionApi { - private val json: Json by injectLazy() private val networkService: NetworkHelper by injectLazy() private val getExtensionRepo: GetExtensionRepo by injectLazy() private val updateExtensionRepo: UpdateExtensionRepo by injectLazy() @@ -47,11 +45,9 @@ internal class ExtensionApi { .newCall(GET("$repoBaseUrl/index.min.json")) .awaitSuccess() - with(json) { - response - .parseAs>() - .toExtensions(repoBaseUrl) - } + response + .parseAs>() + .toExtensions(repoBaseUrl) } catch (e: Throwable) { Logger.e(e) { "Failed to get extensions from $repoBaseUrl" } emptyList() diff --git a/app/src/main/java/yokai/core/di/AppModule.kt b/app/src/main/java/yokai/core/di/AppModule.kt index f53d7c1e1a..70f1f13d92 100644 --- a/app/src/main/java/yokai/core/di/AppModule.kt +++ b/app/src/main/java/yokai/core/di/AppModule.kt @@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.util.chapter.ChapterFilter import eu.kanade.tachiyomi.util.manga.MangaShortcutManager import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory import kotlinx.serialization.json.Json +import kotlinx.serialization.protobuf.ProtoBuf import nl.adaptivity.xmlutil.XmlDeclMode import nl.adaptivity.xmlutil.core.XmlVersion import nl.adaptivity.xmlutil.serialization.XML @@ -145,6 +146,9 @@ fun appModule(app: Application) = module { xmlVersion = XmlVersion.XML10 } } + single { + ProtoBuf + } single { ChapterFilter() } diff --git a/app/src/main/java/yokai/domain/extension/repo/service/ExtensionRepoService.kt b/app/src/main/java/yokai/domain/extension/repo/service/ExtensionRepoService.kt index b07f5d0468..2613d71f38 100644 --- a/app/src/main/java/yokai/domain/extension/repo/service/ExtensionRepoService.kt +++ b/app/src/main/java/yokai/domain/extension/repo/service/ExtensionRepoService.kt @@ -6,17 +6,13 @@ import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.util.system.withIOContext -import kotlinx.serialization.json.Json import okhttp3.OkHttpClient -import uy.kohesive.injekt.injectLazy import yokai.domain.extension.repo.model.ExtensionRepo class ExtensionRepoService( private val client: OkHttpClient, ) { - private val json: Json by injectLazy() - suspend fun fetchRepoDetails( repo: String, ): ExtensionRepo? { @@ -24,12 +20,10 @@ class ExtensionRepoService( val url = "$repo/repo.json".toUri() try { - with(json) { - client.newCall(GET(url.toString())) - .awaitSuccess() - .parseAs() - .toExtensionRepo(baseUrl = repo) - } + client.newCall(GET(url.toString())) + .awaitSuccess() + .parseAs() + .toExtensionRepo(baseUrl = repo) } catch (e: Exception) { Logger.e(e) { "Failed to fetch repo details" } null diff --git a/core/build.gradle.kts b/core/build.gradle.kts index c8fa08982e..d78b8e4392 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -66,7 +66,6 @@ android { tasks { withType { compilerOptions.freeCompilerArgs.addAll( - "-Xcontext-receivers", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", ) diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index d2a4950347..859af0895c 100644 --- a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt +++ b/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.network import java.io.IOException import java.util.concurrent.atomic.AtomicBoolean import kotlin.coroutines.resumeWithException -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.json.Json @@ -18,6 +17,8 @@ import okhttp3.Response import rx.Observable import rx.Producer import rx.Subscription +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get val jsonMime = "application/json; charset=utf-8".toMediaType() @@ -68,7 +69,6 @@ fun Call.asObservableSuccess(): Observable { } // Based on https://github.com/gildor/kotlin-coroutines-okhttp -@OptIn(ExperimentalCoroutinesApi::class) private suspend fun Call.await(callStack: Array): Response { return suspendCancellableCoroutine { continuation -> val callback = @@ -131,13 +131,11 @@ fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: Progre return progressClient.newCall(request) } -context(Json) inline fun Response.parseAs(): T { - return decodeFromJsonResponse(serializer(), this) + return Injekt.get().decodeFromJsonResponse(serializer(), this) } -context(Json) -fun decodeFromJsonResponse( +fun Json.decodeFromJsonResponse( deserializer: DeserializationStrategy, response: Response, ): T { From 85af94d810c4a0863c7bed36e3ab8c72db41fa76 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 1 Jan 2025 07:35:55 +0700 Subject: [PATCH 054/181] fix: Fix build --- .../tachiyomi/data/database/models/ChapterImpl.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt index 7017d0d14c..e9a27c7c53 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt @@ -4,11 +4,11 @@ import eu.kanade.tachiyomi.source.model.SChapter fun SChapter.toChapter(): ChapterImpl { return ChapterImpl().apply { - name = this@SChapter.name - url = this@SChapter.url - date_upload = this@SChapter.date_upload - chapter_number = this@SChapter.chapter_number - scanlator = this@SChapter.scanlator + name = this@toChapter.name + url = this@toChapter.url + date_upload = this@toChapter.date_upload + chapter_number = this@toChapter.chapter_number + scanlator = this@toChapter.scanlator } } From 1461f048ddb627f2e62cf3d2e95befd38e6bc919 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 1 Jan 2025 07:44:09 +0700 Subject: [PATCH 055/181] fix: Resolve deprecated gradle function --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1c987b5cc1..0d1f72e1d2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,7 +23,7 @@ if (gradle.startParameter.taskRequests.toString().contains("standard", true)) { fun runCommand(command: String): String { val byteOut = ByteArrayOutputStream() - project.exec { + providers.exec { commandLine = command.split(" ") standardOutput = byteOut } From 012407aede25491af05b69bfaff185a6bb003459 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 1 Jan 2025 01:11:24 +0100 Subject: [PATCH 056/181] Translations update from Hosted Weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ahmad Ansori Palembani Co-authored-by: Hosted Weblate Co-authored-by: Infy's Tagalog Translations Co-authored-by: zhongfly Co-authored-by: ɴᴇᴋᴏ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai-plurals/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai-plurals/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/fil/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/id/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/zh_Hant/ Translation: Yōkai/Yōkai Translation: Yōkai/Yōkai Plurals --- .../commonMain/moko-resources/ar/plurals.xml | 33 +--- .../commonMain/moko-resources/ar/strings.xml | 8 +- .../commonMain/moko-resources/az/plurals.xml | 8 +- .../commonMain/moko-resources/az/strings.xml | 8 +- .../commonMain/moko-resources/bg/plurals.xml | 25 +-- .../commonMain/moko-resources/bg/strings.xml | 8 +- .../commonMain/moko-resources/bn/plurals.xml | 25 +-- .../commonMain/moko-resources/bn/strings.xml | 8 +- .../commonMain/moko-resources/ca/plurals.xml | 18 +-- .../commonMain/moko-resources/ca/strings.xml | 8 +- .../commonMain/moko-resources/ceb/strings.xml | 7 +- .../commonMain/moko-resources/cs/plurals.xml | 30 +--- .../commonMain/moko-resources/cs/strings.xml | 8 +- .../commonMain/moko-resources/cv/plurals.xml | 14 +- .../commonMain/moko-resources/cv/strings.xml | 3 +- .../commonMain/moko-resources/de/plurals.xml | 29 +--- .../commonMain/moko-resources/de/strings.xml | 8 +- .../commonMain/moko-resources/el/plurals.xml | 29 +--- .../commonMain/moko-resources/el/strings.xml | 8 +- .../commonMain/moko-resources/eo/plurals.xml | 13 +- .../commonMain/moko-resources/eo/strings.xml | 3 +- .../commonMain/moko-resources/es/plurals.xml | 30 +--- .../commonMain/moko-resources/es/strings.xml | 8 +- .../commonMain/moko-resources/eu/plurals.xml | 24 +-- .../commonMain/moko-resources/eu/strings.xml | 7 +- .../commonMain/moko-resources/fa/strings.xml | 8 +- .../commonMain/moko-resources/fi/plurals.xml | 24 +-- .../commonMain/moko-resources/fi/strings.xml | 4 +- .../commonMain/moko-resources/fil/plurals.xml | 30 +--- .../commonMain/moko-resources/fil/strings.xml | 104 +++++++++++- .../commonMain/moko-resources/fr/plurals.xml | 31 +--- .../commonMain/moko-resources/fr/strings.xml | 8 +- .../commonMain/moko-resources/gl/strings.xml | 8 +- .../commonMain/moko-resources/hi/plurals.xml | 16 +- .../commonMain/moko-resources/hi/strings.xml | 8 +- .../commonMain/moko-resources/hr/plurals.xml | 31 +--- .../commonMain/moko-resources/hr/strings.xml | 8 +- .../commonMain/moko-resources/hu/plurals.xml | 17 +- .../commonMain/moko-resources/hu/strings.xml | 6 +- .../commonMain/moko-resources/in/plurals.xml | 29 +--- .../commonMain/moko-resources/in/strings.xml | 11 +- .../commonMain/moko-resources/it/plurals.xml | 31 +--- .../commonMain/moko-resources/it/strings.xml | 8 +- .../commonMain/moko-resources/iw/plurals.xml | 18 +-- .../commonMain/moko-resources/iw/strings.xml | 8 +- .../commonMain/moko-resources/ja/plurals.xml | 29 +--- .../commonMain/moko-resources/ja/strings.xml | 8 +- .../commonMain/moko-resources/jv/strings.xml | 7 +- .../commonMain/moko-resources/ka/strings.xml | 3 +- .../commonMain/moko-resources/kk/plurals.xml | 15 +- .../commonMain/moko-resources/kk/strings.xml | 8 +- .../commonMain/moko-resources/km/plurals.xml | 10 +- .../commonMain/moko-resources/km/strings.xml | 8 +- .../commonMain/moko-resources/ko/plurals.xml | 29 +--- .../commonMain/moko-resources/ko/strings.xml | 9 +- .../commonMain/moko-resources/lt/strings.xml | 3 +- .../commonMain/moko-resources/lv/plurals.xml | 17 +- .../commonMain/moko-resources/lv/strings.xml | 5 +- .../commonMain/moko-resources/mn/strings.xml | 6 - .../commonMain/moko-resources/ms/plurals.xml | 29 +--- .../commonMain/moko-resources/ms/strings.xml | 8 +- .../commonMain/moko-resources/my/plurals.xml | 9 +- .../commonMain/moko-resources/my/strings.xml | 8 +- .../moko-resources/nb-rNO/plurals.xml | 30 +--- .../moko-resources/nb-rNO/strings.xml | 8 +- .../commonMain/moko-resources/ne/plurals.xml | 30 +--- .../commonMain/moko-resources/ne/strings.xml | 8 +- .../commonMain/moko-resources/nl/plurals.xml | 30 +--- .../commonMain/moko-resources/nl/strings.xml | 8 +- .../commonMain/moko-resources/om/plurals.xml | 10 +- .../commonMain/moko-resources/om/strings.xml | 7 +- .../commonMain/moko-resources/pl/plurals.xml | 32 +--- .../commonMain/moko-resources/pl/strings.xml | 8 +- .../moko-resources/pt-rBR/plurals.xml | 31 +--- .../moko-resources/pt-rBR/strings.xml | 17 +- .../commonMain/moko-resources/pt/plurals.xml | 31 +--- .../commonMain/moko-resources/pt/strings.xml | 8 +- .../commonMain/moko-resources/ro/plurals.xml | 31 +--- .../commonMain/moko-resources/ro/strings.xml | 8 +- .../commonMain/moko-resources/ru/plurals.xml | 32 +--- .../commonMain/moko-resources/ru/strings.xml | 8 +- .../commonMain/moko-resources/sc/plurals.xml | 30 +--- .../commonMain/moko-resources/sc/strings.xml | 8 +- .../commonMain/moko-resources/si/strings.xml | 5 - .../commonMain/moko-resources/sk/plurals.xml | 17 +- .../commonMain/moko-resources/sk/strings.xml | 8 +- .../commonMain/moko-resources/sq/strings.xml | 3 +- .../commonMain/moko-resources/sr/plurals.xml | 31 +--- .../commonMain/moko-resources/sr/strings.xml | 8 +- .../commonMain/moko-resources/sv/plurals.xml | 30 +--- .../commonMain/moko-resources/sv/strings.xml | 8 +- .../commonMain/moko-resources/th/plurals.xml | 29 +--- .../commonMain/moko-resources/th/strings.xml | 8 +- .../commonMain/moko-resources/tl/strings.xml | 3 +- .../commonMain/moko-resources/tr/plurals.xml | 30 +--- .../commonMain/moko-resources/tr/strings.xml | 8 +- .../commonMain/moko-resources/uk/plurals.xml | 32 +--- .../commonMain/moko-resources/uk/strings.xml | 8 +- .../commonMain/moko-resources/vi/plurals.xml | 29 +--- .../commonMain/moko-resources/vi/strings.xml | 8 +- .../moko-resources/zh-rCN/plurals.xml | 33 +--- .../moko-resources/zh-rCN/strings.xml | 153 ++++++++++++++---- .../moko-resources/zh-rTW/plurals.xml | 29 +--- .../moko-resources/zh-rTW/strings.xml | 26 ++- 104 files changed, 340 insertions(+), 1530 deletions(-) diff --git a/i18n/src/commonMain/moko-resources/ar/plurals.xml b/i18n/src/commonMain/moko-resources/ar/plurals.xml index 2bdf73bcba..ee1cc5e004 100644 --- a/i18n/src/commonMain/moko-resources/ar/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ar/plurals.xml @@ -8,7 +8,6 @@ يتوفَّر %d تحديثًا للإضافات يتوفَّر %d تحديث للإضافات - لا توجد فئات فئة @@ -17,16 +16,6 @@ %d فئةً %d فئة - - - انتهت الصفحات - تبقَّت صفحة - تبقَّت صفحتان - تبقَّت %1$d صفحات - تبقَّت %1$d صفحةً - تبقَّت %1$d صفحة - - لم يُزل أيُّ فصل من المصدر: \n%2$s @@ -47,7 +36,6 @@ \n%2$s \nأأحذف تنزيلاتهم؟ - ألا أزيل أيَّ فصل؟ أأزيل فصلًا منزَّلًا؟ @@ -56,7 +44,6 @@ أأزيل %1$d فصلًا منزَّلًا؟ أأزيل %1$d فصل منزَّل؟ - بعد %1$s دقيقة بعد %1$s دقيقة @@ -65,7 +52,6 @@ بعد %1$s دقائق بعد %1$s دقائق - تم التنظيف. تمت إزالة %d مجلد تم التنظيف. تمت إزالة %d مجلد @@ -74,7 +60,6 @@ تم التنظيف. تمت إزالة %d مجلدات تم التنظيف. تمت إزالة %d مجلدات - لصفر عنوان لعنوان @@ -83,7 +68,6 @@ ل‍ %d عنوانًا ل‍ %d عنوان - ولا أيِّ فصل وفصل @@ -92,7 +76,6 @@ و %1$d فصلًا و %1$d فصل - تمَّ في %1$s وبدون أخطاء تمَّ في %1$s وفيه خطأ @@ -101,7 +84,6 @@ تمَّ في %1$s وفيه %2$s خطأً تمَّ في %1$s وفيه %2$s خطأ - لا يوجد أيُّ فصل فصل واحد @@ -110,7 +92,6 @@ %1$s فصلًا %1$s فصل - لم تُرحَّل أيُّ سلسلة رُحِّلت السلسلة @@ -119,7 +100,6 @@ رُحِّلت %d سلسلةً رُحِّلت %d سلسلةٍ - ألا أنسخ أيَّ سلسلة؟ أأنسخ السلسلة؟ @@ -128,7 +108,6 @@ أأنسخ %1$d (بتخطي %2$s) سلسلةً؟ أأنسخ %1$d (بتخطي %2$s) سلسلةٍ؟ - ألا أرحِّل أيَّ سلسلة؟ أأرحِّل السلسلة؟ @@ -137,7 +116,6 @@ أأرحِّل %1$d (بتخطِّي %2$s) سلسلةً؟ أأرحِّل %1$d (بتخطِّي %2$s) سلسلةٍ؟ - لا توجد صفحات صفحة @@ -146,7 +124,6 @@ %1$d صفحةً %1$d صفحة - لا يوجد تحديث معلَّق يوجد تحديث معلَّق @@ -155,7 +132,6 @@ يوجد %d تحديثًا معلَّقًا يوجد %d تحديث معلَّق - لم تُحدَّث أيُّ إضافة حُدِّثت إضافة @@ -164,7 +140,6 @@ حُدِّثت %d إضافةً حُدِّثت %d إضافة - لا يُتخطَّى أيُّ فصل يُتخطَّى فصل، وذلك إما لأن المصدر مفقود أو لأنه مصفًّى @@ -173,7 +148,6 @@ يُتخطَّى %d فصلًا، وذلك إما لأن المصدر مفقود أو لأنهم مصفَّون يُتخطَّى %d فصل، وذلك إما لأن المصدر مفقود أو لأنهم مصفَّون - تم تنظيف الملفات المؤقتة. تم حذف %d من الملفات تم تنظيف الملفات المؤقتة. تم حذف %d من الملفات @@ -182,7 +156,6 @@ تم تنظيف الملفات المؤقتة. تم حذف %d من الملفات تم تنظيف الملفات المؤقتة. تم حذف %d من الملفات - لا يوجد فصل تالٍ لم يُقرأ الفصل غير المقروء التالي @@ -191,7 +164,6 @@ %d فصلًا تاليًا لم يُقرؤوا %d فصل تالٍ لم يُقرؤوا - %d انواع المسلسلات %d انواع المسلسلات @@ -200,7 +172,6 @@ %d عدة انواع من المسلسلات %d انواع من المسلسلات - %d مصدر %d مصدر @@ -209,7 +180,6 @@ %d مصادر %d مصادر - %d لغة %d لغة @@ -218,7 +188,6 @@ %d لغات %d لغات - %d وضع %d وضع @@ -227,4 +196,4 @@ %d اوضاع %d اوضاع - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ar/strings.xml b/i18n/src/commonMain/moko-resources/ar/strings.xml index dae24365fe..8fbfe8ca83 100644 --- a/i18n/src/commonMain/moko-resources/ar/strings.xml +++ b/i18n/src/commonMain/moko-resources/ar/strings.xml @@ -753,11 +753,6 @@ حسب أرقام الفصول حسب ترتيب المصدر لم يعلَّم - يتطلب TachiyomiJ2K الوصول إلى جميع الملفات لينزِّل الفصول. اضغط هنا ثم مكِّن «السماح بالوصول لإدارة جميع الملفات.» - يحتاج TachiyomiJ2K للوصول إلى كلِّ الملفَّات في أندرويد ١١، فبذلك ينزِّل الفصول ويحتاط ويقرأ السلاسل المنزَّلة. - \n - \nمكِّن «اسمح بإدارة كافة الملفات.» عندما تراها في الشاشة التالية - إذن الوصول إلى الملفات مطلوب تحذير بحث %1$s الاتجاه @@ -904,7 +899,6 @@ بنفسجي ٥٪ سلسلة وكيل المستخدم المبدئية - تنسيق RARv5 ليس مدعومًا تتطلَّب بعض اللغات إعادة التشغيل لتظهر صحيحةً الفصول المنزَّلة التحميل تلقائيا أثناء القراءة @@ -963,4 +957,4 @@ معلومات التنقيح الأنشطة التي تعمل في الخلفية شارك الغلاف - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/az/plurals.xml b/i18n/src/commonMain/moko-resources/az/plurals.xml index 31d3381bad..622df450e9 100644 --- a/i18n/src/commonMain/moko-resources/az/plurals.xml +++ b/i18n/src/commonMain/moko-resources/az/plurals.xml @@ -4,14 +4,8 @@ Endirilmiş %1$d bölüm silinsin\? Endirilmiş %1$d bölüm silinsin\? - %1$s bölüm %1$s bölüm - - - %1$d səhifə qaldı - %1$d səhifə qaldı - - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/az/strings.xml b/i18n/src/commonMain/moko-resources/az/strings.xml index 1558940540..01f3695acd 100644 --- a/i18n/src/commonMain/moko-resources/az/strings.xml +++ b/i18n/src/commonMain/moko-resources/az/strings.xml @@ -2,7 +2,6 @@ Ad Daha çox - TachiyomiJ2K bölümləri yükləmək üçün bütün fayllara giriş tələb edir. Bura klikləyin, sonra \"Bütün faylları idarə etmək üçün girişə icazə verin\" funksiyasını aktivləşdirin. Manga Komiks Davam edir @@ -40,17 +39,12 @@ Sırala Bölümlər tapılmadı Səhifələr tapılmadı - Fayl icazələri tələb olunur - TachiyomiJ2K bölümləri yükləmək, avtomatik ehtiyat nüsxələri yaratmaq və lokal manqa oxumaq üçün Android 11-də bütün fayllara giriş icazəsi olmalıdır. - \n - \nNövbəti ekranda \"Bütün faylları idarə etmək üçün girişə icazə verin\" seçimini aktivləşdirin. Manhwa Manhua Boş olduqda kilidlə Bölümlər Oxundu olaraq işarələndi Bölümlər silindi. - RARv5 formatı dəstəklənmir Bütün endirilənlər silinsin\? Silmək üçün bölüm yoxdur Mənbə sırasıyla @@ -59,4 +53,4 @@ Skanlyatorlar Kateqoriya Kateqoriyalar - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/bg/plurals.xml b/i18n/src/commonMain/moko-resources/bg/plurals.xml index 92490a53d9..c8dc3ddf74 100644 --- a/i18n/src/commonMain/moko-resources/bg/plurals.xml +++ b/i18n/src/commonMain/moko-resources/bg/plurals.xml @@ -4,77 +4,58 @@ 1 наличен ъпдейт за разширение %d налични ъпдейта за разширения - Направено за %1$s с %2$s грешка Направено за %1$s с %2$s грешки - %d категория %d категории - - - %1$d страница останала - %1$d страници останали - - %1$s глава %1$s глави - Изтри %1$d изтеглената глава\? Изтри %1$d изтеглените глави\? - Има 1 липсваща глава, източникът липса или е филтриран Има %d липсващи глави, източникът липса или е филтриран - Копирайте %1$d%2$s манга\? Копирайте %1$d%2$sманги\? - Разширението е актуализирано %d разширения са актуализирани - %d преместена манга %d преместени манги - Миграция на %1$d%2$s манга\? Миграция на %1$d%2$s манги\? - За %d заглавие За %d заглавия - %d чакаща актуализация %d чакаща актуализации - %1$d страница %1$d страници - и %1$d повече глава и %1$d повече глави - Една глава е премахната от източника: \n%2$s @@ -84,24 +65,20 @@ \n \nИзтриване на изтеглянето им\? - Почистването е завършено. Премахната е папката %d Почистването е завършено. Премахнати са папките %d - След %1$s минута След %1$s минути - Кешът е изчистен. %d файлът е изтрит Кешът е изчистен. %d файловете са изтрити - Следващата непрочетена глава Следващите %d непрочетени глави - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/bg/strings.xml b/i18n/src/commonMain/moko-resources/bg/strings.xml index e656aeb267..a44cce0d55 100644 --- a/i18n/src/commonMain/moko-resources/bg/strings.xml +++ b/i18n/src/commonMain/moko-resources/bg/strings.xml @@ -543,11 +543,7 @@ Бутони за тема, базирани на корицата През всяка мрежа Не актуализирайте автоматично - Необходими са разрешения за файлове Изтрита категория - TachiyomiJ2K изисква достъп до всички файлове в Android 11, за да изтегля глави, да създава автоматични резервни копия и да чете локална манга. - \n - \nНа следващия екран разрешете \"Разрешаване на достъп за управление на всички файлове.\" Добавете категории Няма намерени съвпадения за текущите ви филтри Покажи всички категории @@ -667,7 +663,6 @@ Автоматични актуализации Приложение за автоматично актуализиране Само през Wi-Fi - TachiyomiJ2K изисква достъп до всички файлове, за да изтегли главите. Докоснете тук, след което разрешете \"Разрешаване на достъп за управление на всички файлове.\" Не можа да се инсталира актуализация Завършена актуализация Вашата библиотека @@ -888,7 +883,6 @@ %1$d пропуснати обновление/я Пропусната, понеже не е започната Изчисти данните на WebView - Форматът RARv5 не се поддържа Грешка: празен URI Подобрява производителността на четеца Данните на WebView изчистени @@ -921,4 +915,4 @@ Изключени категории Инсталатор Работа на заден план - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/bn/plurals.xml b/i18n/src/commonMain/moko-resources/bn/plurals.xml index fd06eb40fb..3cdfe28a11 100644 --- a/i18n/src/commonMain/moko-resources/bn/plurals.xml +++ b/i18n/src/commonMain/moko-resources/bn/plurals.xml @@ -4,37 +4,26 @@ এক্সটেনশন আপডেট পাওয়া গেছে %dটি এক্সটেনশন আপডেট পাওয়া গেছে - %d বিভাগ %d বিভাগসমূহ - - - %1$d পৃষ্ঠা বাকি - %1$d গুলি পৃষ্ঠা বাকি - - %1$s সময়ে %2$s ত্রুটি সম্পন্ন %1$s সময়ে %2$sটি ত্রুটি সম্পন্ন - %1$dটি ডাউনলোড করা অধ্যায় সরাবেন\? ডাউনলোড করা %1$dটি অধ্যায় সরাবেন\? - %1$sটি অধ্যায় %1$s গুলি অধ্যায় - 1 অধ্যায় এড়িয়ে জাছি, হয় উৎসটি অনুপস্থিত বা এটি ফিল্টার করা হয়েছে %d অধ্যায় কিপ করা হচ্ছে, হয় উৎসটি তাদের অনুপস্থিত অথবা সেগুলি ফিল্টার করা হয়েছে - উৎস থেকে একটি অধ্যায় সরানো হয়েছে: \n%2$s @@ -44,64 +33,52 @@ \n \nতাদের ডাউনলোড মুছে ফেলবেন\? - %d টি মাঙ্গা স্থানান্তরিত হয়েছে %d টা মাঙ্গা স্থানান্তরিত হয়েছে - %1$d%2$s মাঙ্গা কপি করবেন\? %1$d%2$s টা মাঙ্গা কপি করবেন\? - ক্যাশে সাফ করা হয়েছে। %d ফাইল মুছে ফেলা হয়েছে ক্যাশে সাফ করা হয়েছে। %d টা ফাইল মুছে ফেলা হয়েছে - পরিষ্কার করা হয়েছে। %d ফোল্ডার সরানো হয়েছে পরিষ্কার করা হয়েছে। %d টা ফোল্ডার সরানো হয়েছে - %d শিরোনামের জন্য %d গুল শিরোনামের জন্য - এবং আরো %1$d টি অধ্যায় এবং আরো %1$d গুল অধ্যায় - %d আপডেট মুলতুবি আছে %d গুলো আপডেট মুলতুবি আছে - এক্সটেনশন আপডেট করা হয়েছে %d টি এক্সটেনশন আপডেট করা হয়েছে - %1$dটা পৃষ্ঠা %1$dটি পৃষ্ঠা - %1$d%2$s মাঙ্গা মাইগ্রেট করবেন\? %1$d%2$s টা মাঙ্গা মাইগ্রেট করবেন\? - %1$s মিনিট পরে %1$s টা মিনিট পরে - পরবর্তী অপঠিত অধ্যায় পরবর্তী %d টি অপঠিত অধ্যায় - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/bn/strings.xml b/i18n/src/commonMain/moko-resources/bn/strings.xml index b132c9ec29..da79a8a07f 100644 --- a/i18n/src/commonMain/moko-resources/bn/strings.xml +++ b/i18n/src/commonMain/moko-resources/bn/strings.xml @@ -576,11 +576,6 @@ বিভাগ যোগ করুন/সম্পাদনা করুন %1$s কে এতে সরান… এতে %1$s যোগ করুন… - ফাইল অনুমতি প্রয়োজন - TachiyomiJ2K-এর জন্য অধ্যায়গুলি ডাউনলোড করতে, স্বয়ংক্রিয় ব্যাকআপ তৈরি করতে এবং স্থানীয় মাঙ্গা পড়তে Android 11-এর সমস্ত ফাইল অ্যাক্সেস করতে হবে। -\n -\nপরবর্তী স্ক্রিনে, \"সমস্ত ফাইল পরিচালনা করতে অ্যাক্সেসের অনুমতি দিন\" সক্ষম করুন।\" - TachiyomiJ2K-এর অধ্যায়গুলি ডাউনলোড করার জন্য সমস্ত ফাইলে অ্যাক্সেস প্রয়োজন। এখানে আলতো চাপুন, তারপর \"সমস্ত ফাইল পরিচালনা করতে অ্যাক্সেসের অনুমতি দিন\" সক্ষম করুন৷\" এক্সটেনশন ইনস্টলার হিসাবে Shizuku ব্যবহার করতে Shizuku ইনস্টল করুন এবং শুরু করুন। শিজুকু চলছে না সঙ্কুচিত ডায়নামিক বিভাগগুলিকে নীচে নিয়ে যাও @@ -887,7 +882,6 @@ %1$s দেখান শিরোনামসমুহ হালনাগাদ এড়িয়ে যান অপঠিত অধ্যায়সহ - RARv5 ধরন সমর্থিত নয় অপঠিত অধ্যায় থাকায় এড়িয়ে যাওয়া হয়েছে FAQ এবং নির্দেশনা সিরিজ কে উপরে তুলুন @@ -902,4 +896,4 @@ পুস্তক সংগ্রহ ডিবাগ তথ্য পটভূমি কার্যকলাপ - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ca/plurals.xml b/i18n/src/commonMain/moko-resources/ca/plurals.xml index 31ea29be14..d8403751b6 100644 --- a/i18n/src/commonMain/moko-resources/ca/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ca/plurals.xml @@ -4,52 +4,42 @@ Hi ha una actualització d’una extensió Hi ha actualitzacions de %d extensions - Voleu eliminar %1$d capítol baixat\? Voleu eliminar %1$d capítols baixats\? - %d categoria %d categories - Fet en %1$s amb %2$s error Fet en %1$s amb %2$s errors - %1$s capítol %1$s capítols - S’ha omès %d capítol. És possible que manqui a la font o que hagi estat filtrat S’han omès %d capítols. És possible que manquin a la font o que hagin estat filtrats - Per a %d títol Per a %d títols - i %1$d capítol més i %1$d capítols més - El següent capítol no llegit Els següents %d capítols no llegits - %1$d pàgina %1$d pàgines - S’ha suprimit un capítol d’aquesta font: \n%2$s @@ -59,14 +49,8 @@ \n \nVoleu eliminar-ne les baixades\? - - - %1$d pàgina restant - %1$d pàgines restants - - Després d’%1$s minut Després de %1$s minuts - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ca/strings.xml b/i18n/src/commonMain/moko-resources/ca/strings.xml index 70e4ce6afa..b4441fe170 100644 --- a/i18n/src/commonMain/moko-resources/ca/strings.xml +++ b/i18n/src/commonMain/moko-resources/ca/strings.xml @@ -452,7 +452,6 @@ Categoria Mou %1$s a… Afegeix %1$s a… - Calen permisos de fitxers Categoria suprimida Vés a la categoria Gestiona la categoria @@ -568,20 +567,15 @@ Sense descripció 5% Cadena d’agent d’usuari per defecte - El format RARv5 no està suportat Capítols baixats Baixa automàticament mentre es llegeix Baixa per avançat Només funciona en elements de la biblioteca i si el capítol actual i el següent ja estan baixats - El TachiyomiJ2K requereix l’accés a tots els fitxers per a poder baixar capítols. Premeu aquí i activeu «Permet l’accés a tots els fitxers». No s’ha pogut instal·lar l’actualització Pàgina del llançament La vostra biblioteca Nova versió beta disponible! Actualització completada - El TachiyomiJ2K requereix l’accés a tots els fitxers en Android 11 per a poder baixar capítols, crear còpies de seguretat automàtiques i llegir mangues locals. -\n -\nA la següent pantalla, activeu «Permet l’accés a tots els fitxers». S’ha omès perquè no cal actualitzar la sèrie La cadena d’agent d’usuari no és vàlida Temps de lectura @@ -735,4 +729,4 @@ Configuració de l’aplicació Informació de depuració Activitat en segon pla - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ceb/strings.xml b/i18n/src/commonMain/moko-resources/ceb/strings.xml index e910d89d3e..3f68b3cc73 100644 --- a/i18n/src/commonMain/moko-resources/ceb/strings.xml +++ b/i18n/src/commonMain/moko-resources/ceb/strings.xml @@ -67,11 +67,6 @@ Tuo ug Wala Baliktad Edge - Gikinahanglan ang mga permiso sa file - Ang TachiyomiJ2K nanginahanglan pag-access sa tanan nga mga file sa Android 11 aron ma-download ang mga kapitulo, paghimo og awtomatikong pag-backup, ug pagbasa sa lokal nga manga. -\n -\nSa sunod nga screen, i-enable ang \"Allow access to manage all files.\" - Ang TachiyomiJ2K nanginahanglan og access sa tanan nga mga file aron ma-download ang mga kapitulo. I-tap dinhi, dayon i-enable ang \"Allow access to manage all files.\" Manhwa Tuo ngadto sa wala Bertikal @@ -332,4 +327,4 @@ Installer Mga entri sa basahonan Kalihokan sa background - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/cs/plurals.xml b/i18n/src/commonMain/moko-resources/cs/plurals.xml index eb9035c525..08318c9b2b 100644 --- a/i18n/src/commonMain/moko-resources/cs/plurals.xml +++ b/i18n/src/commonMain/moko-resources/cs/plurals.xml @@ -5,43 +5,31 @@ Dokončeno za %1$s s %2$s chybami Dokončeno za %1$s s %2$s chybami - %d kategorie %d kategorie %d kategorií - - - Zbývá %1$d stránka - Zbývají %1$d stránky - Zbývá %1$d stránek - - %1$s kapitola %1$s kapitoly %1$s kapitol - Odstranit %1$d staženou kapitolu\? Odstranit %1$d stažené kapitoly\? Odstranit %1$d stažených kapitol\? - a %1$d další kapitolu a %1$d další kapitoly a %1$d dalších kapitol - Je dostupná aktualizace rozšíření Jsou dostupné %d aktualizace rozšíření Je dostupných %d aktualizací rozšíření - Jedna kapitola byla z tohoto zdroje odstraněna: \n%2$s @@ -55,100 +43,84 @@ \n \nChcete je smazat\? - %1$d stránka %1$d stránky %1$d stránek - %d aktualizace čeká %d aktualizace čekají %d aktualizací čeká - Přeskočena %d kapitola, buď chybí ve zdroji nebo byla vyfiltrována Přeskočeny %d kapitoly, buď chybí ve zdroji nebo byly vyfiltrovány Přeskočeno %d kapitol, buď chybí ve zdroji nebo byly vyfiltrovány - Rozšíření bylo aktualizováno %d rozšíření byla aktualizována %d rozšíření bylo aktualizováno - Po %1$s minutě Po %1$s minutách Po %1$s minutách - Migrovat %1$d%2$s mangu\? Migrovat %1$d%2$s mangy\? Migrovat %1$d%2$s mang\? - Vyčištění dokončeno. %d odstraněná složka Vyčištění dokončeno. %d odstraněné složky Vyčištění dokončeno. %d odstraněných složek - Další nepřečtená kapitola Další %d nepřečtené kapitoly Dalších %d nepřečtených kapitol - Kopírovat %1$d%2$s mangu\? Kopírovat %1$d%2$s mangy\? Kopírovat %1$d%2$s mang\? - Mezipaměť vymazána. %d soubor byl odstraněn Mezipaměť vymazána. %d soubory byly odstraněny Mezipaměť vymazána. %d souborů bylo odstraněno - %d zdroj %d zdroje %d zdrojů - %d druh sérií %d druhy sérií %d druhů sérií - Pro %d titul Pro %d tituly Pro %d titulů - %d manga migrována %d manga migrovány %d manga migrováno - %d jazyk %d jazyky %d jazyků - %d status %d statusy %d statusů - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/cs/strings.xml b/i18n/src/commonMain/moko-resources/cs/strings.xml index 60fadafcb3..66393f73bf 100644 --- a/i18n/src/commonMain/moko-resources/cs/strings.xml +++ b/i18n/src/commonMain/moko-resources/cs/strings.xml @@ -536,7 +536,6 @@ Podle čísla kapitoly Podle pořadí zdroje Nezaloženo - Je vyžadováno oprávnění k souborům Nastavit jako výchozí Vlastní info o manze Zdroj není nainstalován @@ -618,9 +617,6 @@ Varování: hromadné stahování může vést k tomu, že zdroje zpomalí a/nebo zablokují Tachiyomi. Klepnutím se dozvíte více. Každé 3 dny Snižuje pruhování barev, ale může mít vliv na výkon - TachiyomiJ2K požaduje přístup ke všem souborům v Android 11, aby mohlo stahovat kapitoly, vytvářet zálohy a načíst lokálně uloženou mangu. -\n -\nNa obrazovce povolte \"Povolit aplikaci přístup ke všem souborům.\" Rozšíření čekající na aktualizaci Čtení %1$s Ve skupině @@ -634,7 +630,6 @@ Oříznout okraje (Strankované) Rozdělit dvojité strany Vyplnit vystřihnuté oblasti - TachiyomiJ2K potřebuje přístup k celému úložišti, aby mohlo stahovat kapitoly. Klikněte sem, poté povolte \"Povolit aplikaci přístup k souborům.\" Zobrazovat přečtené kapitoly v Ve skupině a Všechny Sdílet kombinované strany Chytře (podle strany) @@ -905,7 +900,6 @@ 5% Výchozí řetězec uživatelského agenta Některé jazyky mohou pro správné zobrazení vyžadovat opětovné spuštění aplikace - Formát RARv5 není podporován Všechna přečtená manga Ponechejte mangu s přečtenými kapitolami Automatické stahování při čtení @@ -961,4 +955,4 @@ Nastavení aplikace Ladící informace Činnost na pozadí - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/cv/plurals.xml b/i18n/src/commonMain/moko-resources/cv/plurals.xml index 8a2d53ddea..d22780c11a 100644 --- a/i18n/src/commonMain/moko-resources/cv/plurals.xml +++ b/i18n/src/commonMain/moko-resources/cv/plurals.xml @@ -4,44 +4,32 @@ Хушма валли ҫӗнетӳ пур %d хушма валли ҫӗнетӳ пур - %1$s сыпӑк %1$s сыпӑк - %d пухмӑш %d пухмӑш - - - %1$d эл юлчӗ - %1$d эл юлчӗ - - %1$d тиенӗ сыпӑка катертмелле-и\? %1$d тиенӗ сыпӑксене катертмелле-и\? - %1$s,%2$s йӑнӑшпа тӑвӑннӑ%1$s, %2$s йӑнӑшпа тӑвӑннӑ - 1 минут хыҫҫӑн%1$s минут хыҫҫӑн - Ҫӗнӗ сыпӑксем 1 хайлав валли тупӑннӑҪӗнӗ сыпӑксем %d хайлав валли тупӑннӑ - 1 сыпӑк ҫук %d сыпӑк ҫук - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/cv/strings.xml b/i18n/src/commonMain/moko-resources/cv/strings.xml index e9b4bf29a5..02f280b817 100644 --- a/i18n/src/commonMain/moko-resources/cv/strings.xml +++ b/i18n/src/commonMain/moko-resources/cv/strings.xml @@ -398,7 +398,6 @@ Пур ҫӗрте шыра Пур тиеве катертмелле-и\? Ҫак серилӗх валли пурне те пӑрахӑҫла - Файлсене курма ирӗк памалла Сыпӑк шучӗпе Тиесе илни вӑхӑчӗпе Яланхилле @@ -466,4 +465,4 @@ Сӑрӑ Ларткӑч Вулавӑшри серилӗхсем - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/de/plurals.xml b/i18n/src/commonMain/moko-resources/de/plurals.xml index 460dd138be..bfe82030cd 100644 --- a/i18n/src/commonMain/moko-resources/de/plurals.xml +++ b/i18n/src/commonMain/moko-resources/de/plurals.xml @@ -4,37 +4,26 @@ Entferne %1$d heruntergeladenes Kapitel? Entferne %1$d heruntergeladene Kapitel? - %1$s Kapitel %1$s Kapitel - - - %1$d Seite übrig - %1$d Seiten übrig - - %d Kategorie %d Kategorien - Für %d Titel Für %d Titel - und %1$d weiteres Kapitel und %1$d weitere Kapitel - Erweiterungsaktualisierung verfügbar %d Erweiterungsaktualisierungen verfügbar - Ein Kapitel wurde von der Quelle entfernt: \n%2$s @@ -44,84 +33,68 @@ \n \nDiese heruntergeladenen Dateien löschen\? - %1$d%2$s Manga migrieren? %1$d%2$s Manga migrieren? - %1$d%2$s Manga kopieren? %1$d%2$s Manga kopieren? - %d Manga migriert %d Manga migriert - Zwischenspeicher geleert. %d Datei wurde gelöscht Zwischenspeicher geleert. %d Dateien wurde gelöscht - Aufräumen abgeschlossen. %d Ordner entfernt Aufräumen abgeschlossen. %d Ordner entfernt - Nach %1$s Minute Nach %1$s Minuten - Erledigt in %1$s mit %2$s Fehler Erledigt in %1$s mit %2$s Fehlern - %1$d Seite %1$d Seiten - %d Aktualisierung ausstehend %d Aktualisierungen ausstehend - Erweiterung aktualisiert %d Erweiterungen aktualisiert - %d Kapitel wird übersprungen, da die Quelle dieses entweder nicht besitzt, oder weil es rausgefiltert wurde %d Kapitel werden übersprungen, da die Quelle diese entweder nicht besitzt, oder weil sie rausgefiltert wurden - Nächstes ungelesenes Kapitel Nächste %d ungelesene Kapitel - %d Sprache %d Sprachen - %d Quelle %d Quellen - %d Status %d Status - %d Serientyp %d Serientypen - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/de/strings.xml b/i18n/src/commonMain/moko-resources/de/strings.xml index 69d56fed17..45f7f2e0f4 100644 --- a/i18n/src/commonMain/moko-resources/de/strings.xml +++ b/i18n/src/commonMain/moko-resources/de/strings.xml @@ -762,11 +762,6 @@ Globale Aktualisierungen Eine beliebige Serie öffnen Anzahl der Elemente anzeigen - TachiyomiJ2K benötigt zum Herunterladen von Kapiteln Zugriff auf alle Dateien. Tippen Sie hier und aktivieren Sie dann „Zugriff auf die Verwaltung aller Dateien zulassen.“ - TachiyomiJ2K benötigt Zugriff auf alle Dateien in Android 11 um Kapitel herunterzuladen, automatische Backups zu erstellen und lokale Manga zu lesen. -\n -\nAktivieren Sie im nächsten Bildschirm „Zugriff auf die Verwaltung aller Dateien zulassen.“ - Dateiberechtigungen erforderlich Benachrichtigungsinhalt verbergen Quelle wird nicht unterstützt Ausrichtung @@ -940,7 +935,6 @@ 5% Standard-User-Agent-Text Manche Sprachen benötigen möglicherweise einen Neustart der App zur korrekten Anzeige - Das RARv5-Format wird nicht unterstützt Alle gelesenen Manga Manga mit gelesenen Kapiteln behalten Automatisch während dem Lesen herunterladen @@ -997,4 +991,4 @@ App-Einstellungen Debug-Info Hintergrundaktivität - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/el/plurals.xml b/i18n/src/commonMain/moko-resources/el/plurals.xml index 72b3e20cd0..a26e7b82f1 100644 --- a/i18n/src/commonMain/moko-resources/el/plurals.xml +++ b/i18n/src/commonMain/moko-resources/el/plurals.xml @@ -4,67 +4,50 @@ Διαθέσιμη ενημέρωση επέκτασης %d διαθέσιμες ενημερώσεις επεκτάσεων - και %1$d περισσότερο κεφάλαιο και %1$d περισσότερα κεφάλαια - Για %d τίτλο Για %d τίτλους - %d κατηγορία %d κατηγορίες - - - Απομένει %1$d σελίδα - Απομένουν %1$d σελίδες - - Διαγραφή %1$d κατεβασμένου κεφαλαίου; Διαγραφή %1$d κατεβασμένων κεφαλαίων; - Έγινε σε %1$s με %2$s σφάλμα Έγινε σε %1$s με %2$s σφάλματα - Μετά από %1$s λεπτό Μετά από %1$s λεπτά - Ο καθαρισμός ολοκληρώθηκε. Αφαιρέθηκε %d φάκελος Ο καθαρισμός ολοκληρώθηκε. Αφαιρέθηκαν %d φάκελοι - Η προσωρινή μνήμη διαγράφηκε. %d αρχείο διαγράφηκε Η προσωρινή μνήμη διαγράφηκε. %d αρχεία διαγράφηκαν - %d σειρά μετεγκαταστήθηκε %d σειρές μετεγκαταστήθηκαν - Αντιγραφή %1$d%2$s σειρά; Αντιγραφή %1$d%2$s σειρών; - Μετεγκατάσταση %1$d%2$s σειρά; Μετεγκατάσταση %1$d%2$s σειρών; - Ένα κεφάλαιο καταργήθηκε από την πηγή: \n%2$s @@ -74,54 +57,44 @@ \n \nΔιαγραφή των λήψεων τους; - %1$d σελίδα %1$d σελίδες - %1$s κεφάλαιο %1$s κεφάλαια - %d εκκρεμεί ενημέρωση %d εκκρεμούν ενημερώσεις - Η επέκταση ενημερώθηκε %d επεκτάσεις ενημερώθηκαν - Παραλείπεται %d κεφάλαιο, είτε λείπει από την πηγή είτε έχει φιλτραριστεί Παραλείπονται %d κεφάλαια, είτε λείπουν από την πηγή είτε έχουν φιλτραριστεί - Επόμενο αδιάβαστο κεφάλαιο Επόμενα %d αδιάβαστα κεφάλαια - Τύπος σειράς %d τύποι σειρών - Πηγή %d Πηγές - Κατάσταση %d καταστάσεις - Γλώσσα %d γλώσσες - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/el/strings.xml b/i18n/src/commonMain/moko-resources/el/strings.xml index 2f890850bc..5c93a9d755 100644 --- a/i18n/src/commonMain/moko-resources/el/strings.xml +++ b/i18n/src/commonMain/moko-resources/el/strings.xml @@ -741,11 +741,6 @@ Κατά αριθμό κεφαλαίου Κατά σειρά πηγής Χωρίς σελιδοδείκτη - Το TachiyomiJ2K απαιτεί πρόσβαση σε όλα τα αρχεία για να κατεβάσετε κεφάλαια. Πατήστε εδώ και στη συνέχεια, ενεργοποιήστε την επιλογή \"Να επιτρέπεται η πρόσβαση στη διαχείριση όλων των αρχείων.\" - Το TachiyomiJ2K απαιτεί πρόσβαση σε όλα τα αρχεία στο Android 11 για να κατεβάζει κεφάλαια, να δημιουργεί αυτόματα αντίγραφα ασφαλείας και να διαβάζει τις τοπικά αποθηκευμένες σειρές. -\n -\nΣτην επόμενη οθόνη, ενεργοποιήστε την επιλογή \"Να επιτρέπεται η πρόσβαση στη διαχείριση όλων των αρχείων.\" - Απαιτείται άδεια πρόσβασης αρχείων Tako A Calmer You (Δυναμικό) Προειδοποίηση @@ -905,7 +900,6 @@ Βιολετί 5% Προεπιλεγμένη συμβολοσειρά πράκτορα χρήστη - Η μορφή RARv5 δεν υποστηρίζεται Ορισμένες γλώσσες ενδέχεται να απαιτούν επανεκκίνηση της εφαρμογής για να εμφανιστούν σωστά Διατήρηση σειρών με αναγνωσμένα κεφάλαια Όλες οι σειρές που διαβάστηκαν @@ -964,4 +958,4 @@ Εφαρμογή Συνιστάται να επιτρέψετε τις ειδοποιήσεις για να διατηρείται την βιβλιοθήκη σας και την εφαρμογή σας ενημερωμένες. Κοινοποίηση εξωφύλλου - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/eo/plurals.xml b/i18n/src/commonMain/moko-resources/eo/plurals.xml index 8ff7da8010..b20e14c445 100644 --- a/i18n/src/commonMain/moko-resources/eo/plurals.xml +++ b/i18n/src/commonMain/moko-resources/eo/plurals.xml @@ -1,42 +1,31 @@ - - Cetere %1$d paĝo - Cetere %1$d paĝoj - - Forigi %1$d elŝutitan ĉapitron\? Forigi %1$d elŝutitajn ĉapitrojn\? - 1 ĉapitro %1$s ĉapitroj - %d kategorio%d kategorioj - Farita en %1$s kun %2$s eraro Farita en %1$s kun %2$s eraroj - Mankas 1 ĉapitron Mankas %d ĉapitrojn - Post 1 minutoPost %1$s minutoj - Por 1 titoloPor %d titoloj - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/eo/strings.xml b/i18n/src/commonMain/moko-resources/eo/strings.xml index 446eef6ec5..8721108a43 100644 --- a/i18n/src/commonMain/moko-resources/eo/strings.xml +++ b/i18n/src/commonMain/moko-resources/eo/strings.xml @@ -169,7 +169,6 @@ Servoj Ĉi tiu kromaĵo ne estas de la oficiala Tachiyomi kromaĵlisto. Sekura ekrano - Agordi kiel kovrilo Eraro dumo kovrila kunigado Eraro dum kovrila konservado @@ -362,4 +361,4 @@ Devigi malŝlosi Griza Biblioteka kontribuoj - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/es/plurals.xml b/i18n/src/commonMain/moko-resources/es/plurals.xml index 7ca91a260c..b7c186236d 100644 --- a/i18n/src/commonMain/moko-resources/es/plurals.xml +++ b/i18n/src/commonMain/moko-resources/es/plurals.xml @@ -5,37 +5,26 @@ %d actualizaciones de extensiónes disponibles %d actualizaciones de extensiones disponibles - %d categoría %d categorías %d categorías - - - %1$d página restante - %1$d páginas restantes - %1$d páginas restantes - - ¿Eliminar %1$d capítulo descargado\? ¿Eliminar %1$d capítulos descargados\? ¿Eliminar %1$d capítulos descargados\? - y %1$d capítulo más y %1$d capítulos más y %1$d capítulos más - Para %d título Para %d títulos Para %d títulos - Un capítulo ha sido eliminado de la fuente: \n%2$s @@ -49,106 +38,89 @@ \n \n¿Eliminar capítulos descargados\? - Después de %1$s minuto Después de %1$s minutos Después de %1$s minutos - Limpieza realizada. Carpeta %d eliminada Limpieza realizada. Carpetas %d eliminadas Limpieza realizada. Carpetas %d eliminadas - Completada en %1$s con %2$s error Completada en %1$s con %2$s errores Completada en %1$s con %2$s errores - %d serie migrada %d series migradas %d series migradas - ¿Copiar la serie %1$d%2$s\? ¿Copiar las series %1$d%2$s\? ¿Copiar las series %1$d%2$s\? - ¿Migrar la serie %1$d%2$s\? ¿Migrar las series %1$d%2$s\? ¿Migrar las series %1$d%2$s\? - %1$s capítulo %1$s capítulos %1$s capítulos - Caché borrada. Se ha eliminado el archivo %d Caché borrada. Se han eliminado archivos %d Caché borrada. Se han eliminado archivos %d - %1$d página %1$d páginas %1$d páginas - %d actualización pendiente %d actualizaciones pendientes %d actualizaciones pendientes - Extensión actualizada %d extensiones actualizadas %d extensiones actualizadas - Se omite %d capítulo, o bien falta en la fuente o ha sido filtrado Se omiten %d capítulos, o bien faltan en la fuente o han sido filtrados Se omiten %d capítulos, o bien faltan en la fuente o han sido filtrados - El siguiente capítulo sin leer Los siguientes %d capítulos sin leer Los siguientes %d capítulos sin leer - %d fuente %d fuentes %d fuentes - %d Lenguaje %d lenguajes %d Otros lenguajes - %d estado %d estados %d estados - %d tipos de serie %d tipos de series %d tipos de series - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/es/strings.xml b/i18n/src/commonMain/moko-resources/es/strings.xml index 11efa99f35..f0289e1efa 100644 --- a/i18n/src/commonMain/moko-resources/es/strings.xml +++ b/i18n/src/commonMain/moko-resources/es/strings.xml @@ -760,11 +760,6 @@ Actualizaciones globales Abrir una serie aleatoria Mostrar el número de elementos - TachiyomiJ2K requiere acceso a todos los archivos para descargar capítulos. Toque aquí y, acto seguido, active \"Acceso a todos los archivos.\" - TachiyomiJ2K requiere acceso a todos los archivos en Android 11 para descargar los capítulos, crear las copias de seguridad automáticas y leer las series locales. -\n -\nEn la siguiente pantalla, activa \"Permitir el acceso para gestionar todos los archivos\" - Se necesita el permiso de archivos Advertencia Orientación Orientación predeterminada @@ -935,7 +930,6 @@ Violeta 5% Nombre del navegador a usar («user agent») - La app no soporta el formato RARv5 Algunos idiomas pueden requerir un reinicio de la aplicación para mostrarse correctamente Guardar las entradas con los capítulos leídos Todas las series leídas @@ -994,4 +988,4 @@ Información sobre la depuración Actividad en segundo plano Compartir la portada - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/eu/plurals.xml b/i18n/src/commonMain/moko-resources/eu/plurals.xml index afc55abfd9..9dded181a7 100644 --- a/i18n/src/commonMain/moko-resources/eu/plurals.xml +++ b/i18n/src/commonMain/moko-resources/eu/plurals.xml @@ -4,27 +4,22 @@ Kendu kapitulu %1$d\? Kendu %1$d kapitulu\? - Kapitulu bat saltatzen, iturria falta da edo iragazia izan da %d kapitulu saltatzen, iturria falta da edo iragaziak izan dira - %d kategoria %d kategoriak - %1$d orrialdea %1$d orrialdeak - %d manga migratu da %d manga migratu dira - Kapitulu bat iturritik kendua izan da: \n%2$s @@ -34,69 +29,52 @@ \n \nHaien deskargak ezabatu\? - Migratu manga %1$d%2$s\? Migratu %1$d%2$s manga\? - Kopiatu manga %1$d%2$s\? Kopiatu %1$d%2$s manga\? - Garbiketa egina. Karpeta %d kendu da Garbiketa egina. %d karpeta kendu dira - Cachea garbitu da. Fitxategi %d ezabatu da Cachea garbitu da. %d fitxategi ezabatu dira - Minutu %1$s-en ondoren %1$s minuturen ondoren - Luzapena eguneratu da %d Luzapen eguneratu dira - %d tituluarentzako %d tituluentzako - eta kapitulu %1$d gehiago eta %1$d kapitulu gehiago - eguneraketa %d egiteke %d eguneraketa egiteke - Luzapenaren eguneraketa eskuragarri %d Luzapenen eguneraketak eskuragarri - %1$s-n egin da errore %2$s-ekin %1$s-n egin da %2$s errorerekin - %1$s kapitulu %1$s kapituluak - - - Orri %1$d geratzen da - %1$d orri geratzen dira - - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/eu/strings.xml b/i18n/src/commonMain/moko-resources/eu/strings.xml index 1ab5ee28b1..910d3d2233 100644 --- a/i18n/src/commonMain/moko-resources/eu/strings.xml +++ b/i18n/src/commonMain/moko-resources/eu/strings.xml @@ -1,8 +1,5 @@ - TachiyomiJ2K-k Android 11-kako fitxategi guztietara sarrera behar du kapituluak jaitsi, segurtasun kopia automatikoak sortu, eta manga era lokalean irakurtzeko. -\n -\nHurrengo pantailan, aukeratu \"Baimendu fitxategi guztietarako sarrera.\" %1$s eguneratze-zerrendara gehitzen Kategoria ezabatua Kategoria berria sortu @@ -67,8 +64,6 @@ Etiketa Desblokeatu Desblokeatu liburutegira sartzeko - Fitxategi baimenak behar dira - TachiyomiJ2K-k fitxategi guztietara sartzeko baimena behar du kapituluak jaisteko. Egin klik hemen, eta aukeratu \"Baimendu fitxategiak kudeatzeko sarrera.\" Manga Manhwa Manhua @@ -861,4 +856,4 @@ Baztertutako kategoriak Instalatzailea Atzeko planoko ekintzak - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fa/strings.xml b/i18n/src/commonMain/moko-resources/fa/strings.xml index a0bc89601d..58e92453a8 100644 --- a/i18n/src/commonMain/moko-resources/fa/strings.xml +++ b/i18n/src/commonMain/moko-resources/fa/strings.xml @@ -303,7 +303,6 @@ آغاز نشده کامیک مانهوای - محلی پشتیبان گیری خودکار فایل پشتیبان نامعتبر است @@ -444,10 +443,6 @@ پاک کردن سابقه سابقه پاک شد آیا مطمئن هستید؟ تمام سابقه از دست خواهد رفت. - اجازه دسترسی به فایل - تاچیومی J2K نیازمند دسترسی به همه فایل ها در اندوید 11 برای دانلود قسمت ها، ایجاد پشتیبان گیری خودکار، و خواندن مانگاهای محلی( مانگا های موجود در دستگاه شما) است. -\n -\nدر صفحه بعدی، اجازه \"مجاز بودن دسترسی به مدیریت همه پرونده‌ها\" را بدهید. مدیریت اعلان‌ها رد شده راهنمای شروع @@ -501,7 +496,6 @@ نصف کردن خودکار عکس های بلند مناطق قابل لمس نام دسته نمی تواند خالی باشد - TachiyomiJ2K برای دانلود فصل ها نیاز به دسترسی به همه فایل ها دارد. روی اینجا ضربه بزنید، سپس «Allow access to management all files» را فعال کنید. در حال بروز رسانی %1$s به روز رسانی تکمیل شد نکات جستجو به صورت دوره ای نمایش داده می شود. برای جستجوی، پیشنهاد را به مدت طولانی فشار دهید. @@ -541,4 +535,4 @@ نصب کننده کتابخانه ورودی‌ها فعالیت در پس زمینه - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fi/plurals.xml b/i18n/src/commonMain/moko-resources/fi/plurals.xml index 7cd1316e99..2e09ea4171 100644 --- a/i18n/src/commonMain/moko-resources/fi/plurals.xml +++ b/i18n/src/commonMain/moko-resources/fi/plurals.xml @@ -1,56 +1,41 @@ - Laajennospäivitys saatavilla %d laajennospäivitystä saatavilla - %d kategoria %d kategoriaa - - - %1$d sivu jäljellä - %1$d sivua jäljellä - - Poistetaanko %1$d ladattu luku\? Poistetaanko %1$d ladattua lukua\? - Nimikkeelle %d Nimikkeille %d - Valmistui %1$s virheitä löytyi %2$s Valmistui %1$s virheitä löytyi %2$s - Välimuisti tyhjennetty. %d tiedosto on poistettu Välimuisti tyhjennetty. %d tiedostoa on poistettu - %d manga siirretty %d mangaa siirretty - Kopioi %1$d%2$s manga\? Kopioi %1$d%2$s mangasta\? - Siirrä %1$d%2$s manga\? Siirrä %1$d%2$s mangaa\? - Luku on poistettu lähteestä: \n%2$s @@ -60,39 +45,32 @@ \n \nPoistetaanko niiden lataukset\? - ja %1$d lisää luku ja %1$d lisää lukua - %1$s luku %1$s lukua - Siivous tehty. Poistettu %d kansio Siivous tehty. Poistettu %d kansiota - %1$s minuutin jälkeen %1$s minuutin jälkeen - %1$d sivu %1$d sivua - Seuraava lukematon luku Seuraavat %d lukematonta lukua - 1 puuttuva luku %d puuttuvaa lukua - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fi/strings.xml b/i18n/src/commonMain/moko-resources/fi/strings.xml index 27a23de06d..f7427294d6 100644 --- a/i18n/src/commonMain/moko-resources/fi/strings.xml +++ b/i18n/src/commonMain/moko-resources/fi/strings.xml @@ -706,7 +706,6 @@ Merkitse useat luvut luetuiksi Peruuta kaikki tälle sarjalle Sovelluksen pikavalinnat - Tiedoston käyttöoikeudet vaaditaan Piilota ilmoitusten sisältö Lukuun ottamatta: %s Lähdettä ei tueta @@ -782,7 +781,6 @@ Automaattinen lataus luetessa %1$d sarjaa joita ei olla lisätty kirjastoon tietokanassa Parannetut palvelut - RARv5-muoto ei ole tuettu Korkea Alhaisin Ladattua kuvaa ei voitu jakaa @@ -817,4 +815,4 @@ Poissuljetut kategoriat Asentaja Taustatoiminta - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fil/plurals.xml b/i18n/src/commonMain/moko-resources/fil/plurals.xml index 4911fc8ee5..eeaf928852 100644 --- a/i18n/src/commonMain/moko-resources/fil/plurals.xml +++ b/i18n/src/commonMain/moko-resources/fil/plurals.xml @@ -1,81 +1,61 @@ - %1$s kabanata %1$s (na) kabanata - Available na ang pag-update ng extension Available na ang %d na mga update sa extension - %d kategorya %d (na) kategorya - - - %1$d pahina na lang - %1$d (na) pahina na lang - - Tanggalin ang %1$d na-download na kabanata\? Tanggalin ang %1$d (na) na-download na kabanata\? - Na-restore sa loob ng %1$s na may %2$s error Na-restore sa loob ng %1$s na may %2$s (na) error - Nalinis na ang cache. Binura ang %d file Nalinis na ang cache. Binura ang %d (na) file - %d nakabinbing update %d (na) nakabinbing update - Para sa %d serye Para sa %d (na) serye - at %1$d pang kabanata at %1$d pang mga kabanata - Na-update na ang extension Na-update na ang %d na mga extension - %1$d pahina %1$d (na) pahina - Nilaktawan ang %d na kabanata, maaaring ito ay wala sa source o na-filter ang mga ito Nilaktawan ang %d na mga kabanata, maaaring wala sa source o na-filter ang mga ito - Pagkatapos ng %1$s minuto Pagkatapos ng %1$s (na) minuto - Ilipat ang %1$d manga %2$s\? Ilipat ang %1$d (na) manga %2$s\? - Tinanggal sa source ang kabanata: \n%2$s @@ -85,44 +65,36 @@ \n \nBurahin ang mga na-download\? - Nailipat na ang %d manga Nailipat na ang %d (na) manga - Kopyahin ang %1$d manga %2$s\? Kopyahin ang %1$d (na) manga %2$s\? - Tapos na ang paglilinis. Inalis ang %d folder Tapos na ang paglilinis. Inalis ang %d (na) folder - Susunod na hindi pa nababasa na kabanata Susunod na %d di pa nababasa na kabanata - %d uri ng serye %d mga uri ng serye - %d na pinagkukunan %d na mga pinagmumulan - %d na katayuan %d mga katayuan - %d wika %d na mga wika - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fil/strings.xml b/i18n/src/commonMain/moko-resources/fil/strings.xml index 7822b1ecd2..7552093ea2 100644 --- a/i18n/src/commonMain/moko-resources/fil/strings.xml +++ b/i18n/src/commonMain/moko-resources/fil/strings.xml @@ -570,11 +570,6 @@ Panlahatang pag-update Rine-refresh ang mga cover sa aklatan at habang nag-a-update Kusang i-refresh ang mga cover - Kinakailangan ng Yokai ng access sa lahat ng mga file para maka-download ng mga kabanata. I-tap ito, tapos pakibuksan ang \"Payagan ang pamamahala ng lahat ng file.\" - Ang Yokai ay nangangailangan ng access sa lahat ng mga file sa Android 11 upang mag-download ng mga kabanata, gumawa ng mga awtomatikong pag-backup, at magbasa ng lokal na manga. -\n -\nSa susunod na screen, paganahin ang \"Payagan ang pag-access upang pamahalaan ang lahat ng mga file.\" - Kailangan ng permiso sa file Kapag paalpabetong mag-aayos, ayusin nang binabalewala ang mga article (a, an, the) sa simula ng mga pamagat Ayusin nang binabalewala ang mga article Lumipat nang dalawahang pahina @@ -904,7 +899,6 @@ Lila 5% Default na string ng user agent - Di suportado ang format na RARv5 Maaaring mangailangan ang ilang wika ng muling pag-restart ng app upang maipakita ito Patuloy na magbasa ng mga kabanata Mga nabasang manga @@ -960,4 +954,100 @@ Mga setting ng app Impormasyon sa pag-debug Gawaing background - + Iba pang opsiyon + Mag-navigate pataas + Custom na threshold sa hardware bitmap + Umiiral na ang repo na ito! + Bago lamang + I-restart ang aplikasyon + Maligayang Pagdating! + Pumili tayo ng ilang default. Maaari mong palaging baguhin ang mga bagay-bagay sa ibang pagkakataon sa mga setting. + Magsimula + Pumili ng folder kung saan mag-imbak ang %1$s ng mga na-download ng kabanata, mga backup, at higit pa. \n \nInirerekomenda ang isang nakalaang folder. \n \nNapiling folder: %2$s + Pumili ng folder + Nag-a-update mula sa isang mas lumang bersyon at hindi sigurado kung ano ang pipiliin? Sumangguni sa Tachiyomi upgrade na seksyon sa Mihon storage guide para sa higit pang impormasyon. + Gabay sa storage + Kinakailangan + Opsyonal ngunit inirerekomenda + Pahintulot sa pag-install ng mga app + Upang ma-install ang app sa mga update. + Pahintulot sa mga abiso + Maabisuhan para sa mga update sa aklatan at higit pa. + Paggamit ng baterya sa background + Payagan + Walang nakatakdang lokasyon ng storage + Imbalidong lokasyon: %s + Imbalidong lokasyon + Pahina %1$d ng %2$d + Magbukas ng random na serye (Pangkahalatan) + Paganahin ang pagkilos na pag-swipe ng kabanata + Mga update + Ipakita ang queue sa pag-download + Buksan ang huling nabasang kabanata + Gawi ng pag-long tap sa Kamakailan + Buksan ang pangkalahatang paghanap + Gawi ng pag-log tap sa Maghanap + Legasiya + Nagbibigay-daan sa mga extension na ma-install nang walang mga prompt ng user at nagbibigay-daan sa mga awtomatikong pag-update para sa mga device sa ilalim ng Android 12 + Default + Hindi pa ipinapatupad ang legacy installer, kasalukuyang bumabalik sa PackageInstaller (Default) + Maaaring basahin ng mga mapanganib na extension ang anumang nakatagong kredensyal sa pag-log in o magsagawa ng arbitrary code. \n \nSa pamamagitan ng pagtitiwala sa extension na ito, tinatanggap mo ang mga panganib na ito. + Bawiin ang mga pinagkakatiwalaang hindi kilalang extension + Bawiin lahat ng mga pinagkatiwalaang mga extension? + I-crop ang mga border (Pahabang strip) + Buksan ang mga setting ng legasiyang cutout + Sa mga device na mas luma sa Android 9.0, kailangan mong itakda nang manu-mano ang mga setting ng cutout sa pamamagitan ng mga setting ng sistema + Ipakita ang nilalaman sa lugar ng cutout + Lumikha ng folder base sa titulo ng manga + Default (%d) + I-double tap para mag-zoom + I-share ang cover + Datos at storage + Lokasyon ng storage + Paggamit ng storage + Na magagamit: %1$s / Kabuuan: %2$s + Kasali ang mga sensitibong setting (hal. mga tracker login token) + Huling awtomatikong na-back up: %s + I-print ang detalyadong mga log sa system log (nakakabawas sa performance ng app) + Mode sa pag-debug + Verbose na pagla-log + Maghanap sa mga external na storage para sa mga entry + Mga Repo ng Extension + Magdagdag ng repo + Idagdag ang repo + Di-wastong URL ng repo + Hindi ka pa nagdaragdag ng anumang mga repo. + Tanggalin ang repo? + Gusto mo bang tanggalin ang repo na \"%s\"? + Palitan + Umiiral na ang Signing Key Fingerprint + Ang repository na %1$s ay may magkaparehong Signing Key Fingerprint sa %2$s. \nKung ito ay inaasahan, %2$s ang papalitan, kung hindi naman ay makipag-ugnayan sa tagapamahala ng iyong repo. + Open source na repo + Hindi mabuksan ang url + Ilagay sa ibaba ang serye + Awtomatikong idagdag ang ID + I-tap dito para sa tulong sa Cloudflare + Kinakailangan ng app ang WebView upang gumana ito + Nabigong makakuha ng patuloy na pag-access ng folder. Ang app ay magkaroon ng di-inaasahang pagkilos. + Bumalik + I-refresh + Ilapat + Sumulong + Webtoon + Internal na error: %s + SFW + NSFW + Uri ng nilalaman + Isang Hindi Inaasahang Error ang Naganap + Nagkaroon ng hindi inaasahang error ang %s. Iminumungkahi naming i-screenshot mo ang mensaheng ito, i-dump ang mga crash log, at pagkatapos ay ibahagi ito sa isang GitHub Issue. + I-refresh + Pahabang strip + Buksan ang mga extension / menu ng paglilipat + Ma-iwasan ang mga hadlang sa mahahabang pag-update ng aklatan, pag-download, at pag-restore ng mga backup. + Napili + Kung naglo-load ang reader ng isang blangkong imahe ay unti-unting bawasan ang threshold.\nNapili: %s + Walang napili + I-save ang mga pahina sa magkakaibang folder + Custom na profile sa display + Maaaring maayos ang isyu sa mga na-download na kabanata na magkasalungat sa isa\'t isa kapag pareho sila ng pangalan + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fr/plurals.xml b/i18n/src/commonMain/moko-resources/fr/plurals.xml index 537f5e5a77..d5486db060 100644 --- a/i18n/src/commonMain/moko-resources/fr/plurals.xml +++ b/i18n/src/commonMain/moko-resources/fr/plurals.xml @@ -1,78 +1,60 @@ - Mise à jour d\'extension disponible %d mises à jour des extensions disponibles %d mises à jour des extensions disponibles - et %1$d autre chapitre et %1$d autres chapitres et %1$d autres chapitres - Pour le titre du %d Pour les titres des %d Pour les titres des %d - Supprimer %1$d chapitre téléchargé \? Supprimer %1$d chapitres téléchargés \? Supprimer %1$d chapitres téléchargés \? - %d catégorie %d catégories %d catégories - - - %1$d page restante - %1$d pages restantes - %1$d pages restantes - - Après %1$s minute Après %1$s minutes Après %1$s minutes - Nettoyage terminé. %d dossier supprimé Nettoyage terminé. %d dossiers supprimés Nettoyage terminé. %d dossiers supprimés - Cache effacé. %d fichier a été supprimé Cache effacé. %d fichiers ont été supprimés Cache effacé. %d fichiers ont été supprimés - %d manga migré %d mangas migrés %d mangas migrés - Copier le manga %1$d%2$s \? Copier les mangas %1$d%2$s \? Copier les mangas %1$d%2$s \? - Migrer le manga %1$d%2$s \? Migrer les mangas %1$d%2$s \? Migrer les mangas %1$d%2$s \? - Un chapitre a été retiré de la source : \n%2$s @@ -86,70 +68,59 @@ \n \nSupprimer les téléchargements \? - Effectuée en %1$s avec %2$s erreur Effectuée en %1$s avec %2$s erreurs Effectuée en %1$s avec %2$s erreurs - %1$s chapitre %1$s chapitres %1$s chapitres - %1$d page %1$d pages %1$d pages - %d mise à jour en attente %d mises à jour en attente %d mises à jour en attente - Extension mise à jour %d extensions mises à jour %d extensions mises à jour - %d chapitre a été sauté, soit la source ne l\'a pas, soit il a été filtré %d chapitres ont été sautés, soit la source ne les a pas, soit ils ont été filtrés %d chapitres ont été sautés, soit la source ne les a pas, soit il ont été filtrés - Chapitre suivant non lu Les %d suivants non lus Les %d suivants non lus - %d type de série %d types de série %d types de série - %d source %d sources %d sources - %d état %d états %d états - %d langue %d langues %d langues - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fr/strings.xml b/i18n/src/commonMain/moko-resources/fr/strings.xml index 920addd442..63ae623199 100644 --- a/i18n/src/commonMain/moko-resources/fr/strings.xml +++ b/i18n/src/commonMain/moko-resources/fr/strings.xml @@ -740,10 +740,6 @@ Surfaces coupées du bloc Action d\'un appui long sur le filtre par Catégorie Tout annuler pour cette série - Pour télécharger des chapitres, TachiyomiJ2K nécessite l\'accès à tous les fichiers. Appuyez ici, puis activez « Autoriser l\'accès pour gérer tous les fichiers.» - Sous Android 11, TachiyomiJ2K nécessite l\'accès à tous les fichiers pour pouvoir télécharger des chapitres, créer des sauvegardes automatiques et lire des manga locaux. -\n -\nSur l\'écran suivant, activez « Autoriser l\'accès pour gérer tous les fichiers.» Inclure : %s Cela forcera le recalcul du cache de téléchargement. Utile si vous avez modifié des fichiers téléchargés en dehors de l\'application et que vous souhaitez que l\'application les récupère Rafraichir le cache de téléchargement @@ -767,7 +763,6 @@ Mode sombre noir pur Alignement de l\'icône de navigation latérale Fraises au chocolat - Autorisations de fichiers requises Sélectionner par défaut Par date de téléversement Non mis en favoris @@ -939,7 +934,6 @@ 5% Agents utilisateurs par défaut Certains langages peuvent nécessiter un redémarrage de l\'application pour s\'afficher correctement - Le format RARv5 n\'est pas supporté Téléchargement anticipé pendant la lecture Chapitres téléchargés Stat @@ -992,4 +986,4 @@ Entrées de la bibliothèque Informations de débogage Activité en arrière-plan - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/gl/strings.xml b/i18n/src/commonMain/moko-resources/gl/strings.xml index a549007f45..91188e0dd7 100644 --- a/i18n/src/commonMain/moko-resources/gl/strings.xml +++ b/i18n/src/commonMain/moko-resources/gl/strings.xml @@ -228,12 +228,7 @@ Capítulo %1$s Todos os capítulos lidos Scanlators - TachiyomiJ2K require acceso a tódolos arquivos de Android 11 para descargar capítulos, facer copias de seguridade automáticas e ler manga en local. -\n -\nNa seguinte pantalla, marca \"Permitir acceso á xestión de tódolos arquivos.\" - Requírense permisos de arquivo Non gardado nos marcadores - TachiyomiJ2K require acceso a tódolos arquivos para descargar capítulos, Preme eiquí, e despóis marca \"Permitir acceso á xestión de tódolos arquivos.\" Autor Move %1$s a… Selección inversa @@ -288,7 +283,6 @@ Borráronse as entradas Baixar capítulos novos Elexir a imaxe de portada - O formato RARv5 non está soportado Oimitiuse porque hai capítulos sin ler Actualizar o seguemento Erros @@ -494,4 +488,4 @@ Borrar o historial dos elementos que non estean gardados na túa biblioteca Información de depuración Actividade en segundo plano - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hi/plurals.xml b/i18n/src/commonMain/moko-resources/hi/plurals.xml index 837756dda0..0d82bf18d0 100644 --- a/i18n/src/commonMain/moko-resources/hi/plurals.xml +++ b/i18n/src/commonMain/moko-resources/hi/plurals.xml @@ -1,53 +1,39 @@ - एक्सटेंशन अपडेट उपलब्ध%d एक्सटेंशन अपडेट उपलब्ध - %1$s में %2$s त्रुटि के साथ किया गया%1$s में %2$s त्रुटियों के साथ किया गया - %d श्रेणी %d श्रेणियाँ - %1$s मिनट के बाद%1$s मिनट के बाद - %d अध्याय को छोड़ा जा रहा है, या तो स्रोत में यह नहीं है या इसे फ़िल्टर कर दिया गया है %d अध्यायों को छोड़ा जा रहा है, या तो स्रोत उन्हें याद कर रहा है या उन्हें फ़िल्टर कर दिया गया है - अध्याय %1$s अध्यायों %1$s - - - %1$d पृष्ठ बाकि - %1$d पृष्ठ बाकि - - %1$d डाउनलोड अध्याय को निकालें\? %1$d डाउनलोड अध्यायों को निकालें\? - %d शीर्षक के लिए %d शीर्षकों के लिए - अगला अपठित अध्याय अगले %d अपठित अध्याय - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hi/strings.xml b/i18n/src/commonMain/moko-resources/hi/strings.xml index 2c5688ea6d..ea20c9328c 100644 --- a/i18n/src/commonMain/moko-resources/hi/strings.xml +++ b/i18n/src/commonMain/moko-resources/hi/strings.xml @@ -442,13 +442,8 @@ मानहुआ कॉमिक नए अध्याय - फाइल की अनुमति चाहिए - ताचियोमी को एंड्राइड ११ में फाइल अनुमति चाहिए ताकि वो चैप्टर डाउनलोड कर सके, आटोमेटिक बैकअप बना सके और लोकल माँगा पढ़ सके -\n -\nअगले स्क्रीन पर, सारे फाइल को एक्सेस करने की अनुमति को सेलेक्ट करे मानह्वा शुरू नहीं हुआ हैं - ताचियोमी ज २ क को सारे फाइल को एक्सेस करने की अनुमति चाहिए ताकि वो नए चैप्टर डाउनलोड कर सके. यहाँ पे क्लिक करके \" सारे फाइल को एक्सेस करने की अनुमति दे \" चालु हैं लाइब्रेरी तक पहुंचने के लिए अनलॉक करें अंतिम पढ़ा अध्याय %1$s @@ -545,7 +540,6 @@ ५% डाउनलोड किए गए अध्याय आपके फ़िल्टर के लिए कोई मिलान नहीं - RARv5 प्रारूप समर्थित नहीं है आगे डाउनलोड करें पढ़ते समय ऑटो डाउनलोड करे होल्ड लिस्ट में @@ -587,4 +581,4 @@ लाइब्रेरी के आइटम डीबग जानकारी पृष्ठभूमि गतिविधि - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hr/plurals.xml b/i18n/src/commonMain/moko-resources/hr/plurals.xml index 1a51514d75..41fdf79c9d 100644 --- a/i18n/src/commonMain/moko-resources/hr/plurals.xml +++ b/i18n/src/commonMain/moko-resources/hr/plurals.xml @@ -1,66 +1,50 @@ - %1$s poglavlje %1$s poglavlja %1$s poglavlja - Dostupna je %d nova verzija proširenja Dostupne su %d nove verzije proširenja Dostupoe je %d novih verzija proširenja - %d kategorija %d kategorije %d kategorija - - - %1$d preostala stranica - %1$d preostale stranice - %1$d preostalih stranica - - Ukloniti %1$d preuzeto poglavlje\? Ukloniti %1$d preuzeta poglavlja\? Ukloniti %1$d preuzetih poglavlja\? - i još %1$d naslov i još %1$d naslova i još %1$d naslova - Za %d naslov Za %d naslova Za %d naslova - Brisanje je gotovo. Uklonjena je %d mapa Brisanje je gotovo. Uklonjene su %d mape Brisanje je gotovo. Uklonjeno je %d mapa - Nakon %1$s minute Nakon %1$s minute Nakon %1$s minuta - Obavljeno u %1$s s %2$s greškom Obavljeno u %1$s s %2$s greške Obavljeno u %1$s s %2$s grešaka - Iz izvora je uklonjeno jedno poglavlje: \n%2$s @@ -72,82 +56,69 @@ \n%2$s \nŽeliš li izbrisati njihova preuzimanja\? - Predmemorija je izbrisana. %d datoteka je izbrisana Predmemorija je izbrisana. %d datoteke su izbrisane Predmemorija je izbrisana. %d datoteka je izbrisano - %d serija migrirana %d serije migrirane %d serija migrirano - Kopirati %1$d%2$s seriju\? Kopirati %1$d%2$s serije\? Kopirati %1$d%2$s serija\? - Migrati %1$d%2$s seriju\? Migrati %1$d%2$s serije\? Migrati %1$d%2$s serija\? - %1$d stranica %1$d stranice %1$d stranica - %d aktualiziranje na čekanju %d aktualiziranja na čekanju %d aktualiziranja na čekanju - %d proširenje aktualizirano %d proširenja aktualizirana %d proširenja aktualizirano - Preskače se %d poglavlje. Ne postoji u izvoru ili je filtrirano Preskaču se %d poglavlja. Ne postoje u izvoru ili su filtrirana Preskače se %d poglavlja. Ne postoje u izvoru ili su filtrirana - Sljedeće nepročitano poglavlje Sljedeća %d nepročitana poglavlja Sljedećih %d nepročitanih poglavlja - %d vrsta serije %d vrste serije %d vrsta serije - %d stanje %d stanja %d stanja - %d izvor %d izvora %d izvora - %d jezik %d jezika %d jezika - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hr/strings.xml b/i18n/src/commonMain/moko-resources/hr/strings.xml index c1d6ed84f4..f4b8c28558 100644 --- a/i18n/src/commonMain/moko-resources/hr/strings.xml +++ b/i18n/src/commonMain/moko-resources/hr/strings.xml @@ -706,10 +706,6 @@ Označi raspon poglavlja kao pročitana Prečaci programa Prekini sve za ovu seriju - TachiyomiJ2K zahtijeva pristup svim datotekama u Androidu 11 za preuzimanje poglavlja, za stvaranje automatskih sigurnosnih kopija i za čitanje lokalnih serija. -\n -\nU sljedećem ekranu aktiviraj „Dopusti pristup za upravljanje svim datotekama”. - Potrebne su dozvole za datoteke Globalna aktualiziranja Otvaranje slučajne serije Prikaži broj elemenata @@ -719,7 +715,6 @@ Po imenu izvora Po broju poglavlja Nije zabilježeno - TachiyomiJ2K zahtijeva pristup svim datotekama za preuzimanje poglavlja. Dodirni ovdje, zatim uključi „Dopusti pristup za upravljanje svim datotekama”. Neki proizvođači imaju dodatna programska ograničenja koja onemogućuju pozadinske usluge. Ova web-stranica sadrži daljnje informacije o tome kako to popraviti. Ovo će prisiliti ponovno izračunavanje predmemorije preuzimanja. Korisno, ako su preuzimanja promijenjena izvan ovog programa i ako želiš da ih program preuzme Upozorenje @@ -904,7 +899,6 @@ Otvori u programu 5 % Neki jezici mogu zahtijevati ponovno pokretanje aplikacije za ispravan prikaz - RARv5 format nije podržan Sve pročitane serije Standardni izraz korisničkog agenta Čuvaj unose s pročitanim poglavljima @@ -962,4 +956,4 @@ Postavke aplikacije Informacije otklanjanja grešaka Aktivnost u pozadini - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hu/plurals.xml b/i18n/src/commonMain/moko-resources/hu/plurals.xml index 7888aabf78..1fa80616bc 100644 --- a/i18n/src/commonMain/moko-resources/hu/plurals.xml +++ b/i18n/src/commonMain/moko-resources/hu/plurals.xml @@ -1,58 +1,43 @@ - Egy új bővítményfrissítés érhető el%d bővítményfrissítés érhető el - %d kategória %d kategóriák - - - %1$d oldal hátra - %1$d oldalak hátra - - %1$s fejezet %1$s fejezetek - Eltávolítasz %1$d letöltött fejezetet\? Eltávolítasz %1$d letöltött fejezeteket\? - Befejezve %1$s alatt, %2$s hibával Befejezve %1$s alatt, %2$s hibával - 1 perc után%1$s percek után - %d-nak/nek%d-nak/nek - és még %1$d fejezet és még %1$d fejezet - Következő olvasatlan fejezet Következő %d olvasatlan fejezet - %d fejezet kihagyása, hiányzik a forrás, vagy ki lett szűrve %d fejezet kihagyása, hiányoznak a források, vagy ki lettek szűrve - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hu/strings.xml b/i18n/src/commonMain/moko-resources/hu/strings.xml index 8f64bf7ce0..7a7cd51b75 100644 --- a/i18n/src/commonMain/moko-resources/hu/strings.xml +++ b/i18n/src/commonMain/moko-resources/hu/strings.xml @@ -504,13 +504,10 @@ Elkezdett Nem bejelentkezett trackerek: Művész - Fájl engedély szükséges - A TachiyomiJ2K a fejezetek letöltéséhez hozzáférést igényel az összes fájlhoz. Koppintson ide, majd engedélyezze a \"Hozzáférés engedélyezése az összes fájl kezeléséhez\" Az %1$s áthelyezése ide… %1$s hozzáadása a… Feloldás a könyvtárhoz való hozzáféréshez Felold - A TachiyomiJ2K a fejezetek letöltéséhez, automatikus biztossági mentés létrehozásához és a hely manga olvasásához hozzáférést igényel az összes fájlhoz. A következő képernyőn engedélyeze \"Hozzáférés engedélyezése az összes fájl kezeléséhez\" Nyelv jelvények Nincs elkezdve Nincs találat a jelenlegi filterre @@ -535,7 +532,6 @@ Sorozat típus 5% Alap hálózati kliens szöveg - RARv5 formátum nem támogatót Letölzozz fejezetek Kezdése gomb elrejtése Több könyvtár beállítás @@ -569,4 +565,4 @@ Könyvtár bejegyzések Debug információ Háttér aktivitás - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/in/plurals.xml b/i18n/src/commonMain/moko-resources/in/plurals.xml index 3f8834211e..0d01847b55 100644 --- a/i18n/src/commonMain/moko-resources/in/plurals.xml +++ b/i18n/src/commonMain/moko-resources/in/plurals.xml @@ -1,102 +1,75 @@ - Terdapat %d perbaharuan ekstensi - dan %1$d bab lain - Untuk %d judul - %d kategori - - - %1$d halaman tersisa - - Hapus %1$d bab yang diunduh\? - Selesai dalam %1$s dengan %2$s kesalahan - %1$s bab - %1$s bab telah dihapus dari sumber: \n%2$s \n \nHapus unduhan\? - %d manga dimigrasi - Salin manga %1$d%2$s\? - Pembersihan selesai. %d folder telah dihapus - Cache telah dibersihkan. %d berkas telah dihapus - %1$d halaman - Setelah %1$s menit - Migrasi %1$d%2$s manga\? - Pembaruan %d ditunda - %d - Melewati %d bab, entah sumbernya hilang atau telah difilter - Selanjutnya chapter %d yang belum dibaca - %d jenis seri - %d sumber - %d status - %d bahasa - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/in/strings.xml b/i18n/src/commonMain/moko-resources/in/strings.xml index 1a12821c17..3e59525baa 100644 --- a/i18n/src/commonMain/moko-resources/in/strings.xml +++ b/i18n/src/commonMain/moko-resources/in/strings.xml @@ -706,7 +706,6 @@ Aktifkan %s Tidak ada Alternatif Ditemukan Tidak ada bab yang ditemukan, manga ini tidak dapat digunakan untuk migrasi - Perizinan berkas diperlukan Beberapa pabrikan mempunyai batasan aplikasi tambahan yang mematikan layanan latar belakang. Website ini memiliki info lebih lanjut untuk memperbaikinya. Hal ini akan memaksa cache unduhan untuk mengkalkulasi ulang. Berguna jika Anda memodifikasi unduhan di luar aplikasi ini dan ingin aplikasi ini untuk membacanya Beberapa ekstensi masih mengingatkan untuk dipasang terlebih dulu. @@ -757,10 +756,6 @@ Tampilkan jumlah item Pembaruan selesai Tak ditandai - TachiyomiJ2K membutuhkan akses semua berkas untuk mengunduh bab. Ketuk di sini, lalu izinkan \"Izinkan akses untuk mengatur semua berkas.\" - TachiyomiJ2K membutuhkan akses ke semua berkas di Android 11 untuk mengunduh bab, membuat cadangan otomatis, dan memuat manga lokal. -\n -\nPada layar selanjutnya, izinkan \"Izinkan akses untuk mengatur semua berkas\" Pembatasan: %1$s Hapus %1$s dari %2$s dan tambahkan %3$s Dipasang baru-baru ini @@ -905,7 +900,6 @@ 5% String agen pengguna default Beberapa bahasa mungkin memerlukan peluncuran ulang aplikasi untuk ditampilkan dengan benar - Format RARv5 tidak didukung Semua manga yang dibaca Simpan manga dengan membaca chapter Unduh otomatis ketika sedang membaca @@ -959,4 +953,7 @@ Entri pustaka Info debug Aktivitas dibelakang layar - + Selamat datang! + Opsi lebih lanjut + Segarkan + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/it/plurals.xml b/i18n/src/commonMain/moko-resources/it/plurals.xml index 377f9a40b8..286ab7078b 100644 --- a/i18n/src/commonMain/moko-resources/it/plurals.xml +++ b/i18n/src/commonMain/moko-resources/it/plurals.xml @@ -1,66 +1,50 @@ - Aggiornamento estensione disponibile %d estensioni hanno aggiornamenti disponibili %d estensioni hanno aggiornamenti disponibili - %d categoria %d categorie %d categorie - - - %1$d pagina rimasta - %1$d pagine rimaste - %1$d pagine rimaste - - Rimuovere %1$d capitolo scaricato\? Rimuovere %1$d capitoli scaricati\? Rimuovere %1$d capitoli scaricati\? - Dopo un minuto Dopo %1$s minuti Dopo %1$s minuti - Eliminazione completata. Rimossa la cartella %d Eliminazione completata. Rimosse le cartelle %d Eliminazione completata. Rimosse le cartelle %d - Cache libera. Il file %d è stato rimosso Cache libera. I file %d sono stati rimossi Cache libera. I file %d sono stati rimossi - Una serie migrata %d serie migrate %d serie migrate - Copiare la serie %1$d%2$s\? Copiare le serie %1$d%2$s\? Copiare le serie %1$d%2$s\? - Migrare la serie %2$s\? Migrare le serie %1$d%2$s\? Migrare le serie %1$d%2$s\? - Un capitolo è stato rimosso dalla sorgente: \n%2$s @@ -74,82 +58,69 @@ \n \nEliminare i loro download\? - e un altro capitolo e %1$d altri capitoli e %1$d altri capitoli - Per un titolo Per %d titoli Per %d titoli - Completato in %1$s con %2$s errore Completato in %1$s con %2$s errori Completato in %1$s con %2$s errori - %1$s capitolo %1$s capitoli - %1$d pagina %1$d pagine %1$d pagine - %d aggiornamento in attesa %d aggiornamenti in attesa %d aggiornamenti in attesa - Estensione aggiornata %d estensioni aggiornate %d estensioni aggiornate - %d capitolo saltato, la fonte non ce l\'ha o è stato filtrato %d capitoli saltati, la fonte non li ha o sono stati filtrati %d capitoli saltati, la fonte non li ha o sono stati filtrati - Il prossimo capitolo non letto I prossimi %d capitoli non letti I prossimi %d capitoli non letti - Un tipo di serie %d tipi di serie %d tipi di serie - Fonte %d fonti %d fonti - Status %d stati %d stati - Una lingua %d lingue %d lingue - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/it/strings.xml b/i18n/src/commonMain/moko-resources/it/strings.xml index 1240577768..454e1333bd 100644 --- a/i18n/src/commonMain/moko-resources/it/strings.xml +++ b/i18n/src/commonMain/moko-resources/it/strings.xml @@ -749,11 +749,6 @@ Nascondi badge non letti Apri una serie casuale Mostra il numero di oggetti - TachiyomiJ2K richiede accesso a tutti i file per scaricare i capitoli. Fai tap qui, poi fornisci l\'autorizzazione per \"Accesso a tutti i file.\" - TachiyomiJ2K richiede accesso ai file per scaricare capitoli, creare backup automatici e leggere serie in locale. -\n -\nAlla prossima schermata, fornisci l\'autorizzazione per \"Accesso a tutti i file.\" - Permessi per la gestione dei file richiesti Disattiva %s Attiva %s Selezione @@ -938,7 +933,6 @@ 5% Stringa user agent predefinita del browser Alcune lingue potrebbero richiedere un riavvio dell\'app per esserve visualizzate in maniera corretta - Il formato RARv5 non è supportato Tieni le voci con capitoli letti Tutte le serie lette Capitoli scaricati @@ -996,4 +990,4 @@ Attività in background Consente l\'installazione delle estensioni senza richieste da parte dell\'utente e abilita gli aggiornamenti automatici per i dispositivi con Android 12 Condividi copertina - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/iw/plurals.xml b/i18n/src/commonMain/moko-resources/iw/plurals.xml index 786dab276e..3302df72d6 100644 --- a/i18n/src/commonMain/moko-resources/iw/plurals.xml +++ b/i18n/src/commonMain/moko-resources/iw/plurals.xml @@ -1,73 +1,57 @@ - קטגורייה אחת שתי קטגוריות %d קטגוריות - זמין עדכון לתוסף זמינים עדכונים ל-%d תוספים זמינים עדכונים ל-%d תוספים - הושלם ב %1$s עם שגיאה אחת הושלם ב %1$s עם שתי שגיאות הושלם ב %1$s עם %2$s שגיאות - לאחר דקה אחת לאחר שתי דקות לאחר %1$s דקות - הסר %1$d פרק מההורדות\? הסר %1$d פרקים מההורדות\? הסר %1$d פרקים מההורדות\? הסר %1$d פרקים מההורדות\? - %1$s פרק %1$s פרקים %1$s פרקים %1$s פרקים - - - דף %1$d נותר - %1$d דפים נותרו - %1$d דפים נותרו - %1$d דפים נותרו - - עבור כותר אחד עבור שני כותרים עבור %d כותרים - דולג פרק אחד, המקור חסר או שהוא סונן החוצה דולגו שני פרקים, המקור חסר או שהם סוננו החוצה דולגו %d פרקים, המקור חסר או שהם סוננו החוצה - הפרק הבא שלא נקרא שני הפרקים הבאים שלא נקראו %d הפרקים הבאים שלא נקראו - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/iw/strings.xml b/i18n/src/commonMain/moko-resources/iw/strings.xml index eb364ef8be..a45df8318d 100644 --- a/i18n/src/commonMain/moko-resources/iw/strings.xml +++ b/i18n/src/commonMain/moko-resources/iw/strings.xml @@ -1,10 +1,5 @@ - נדרשת הרשאת גישה לקבצים - טאצ\'יומי J2K דורש גישה לכל הקבצים של אנדרואיד 11 כדי להוריד פרקים, צור גיבויים אוטומטיים, וקרא מנגה מקומית. -\n -\nבמסך הבא, אפשר \"תן גישה לכל הקבצים.\" - TachiyomiJ2K דורש גישה לכל הקבצים כדי להוריד פרקים. הקש כאן ולאחר מכן הפעל את \"אפשר גישה לניהול כל הקבצים.\" לעולם לא החדש ביותר הבא @@ -426,7 +421,6 @@ מדריך נדידת מקורות החרגה: %s %1$d עדכונים נכשלו - הפורמט RARv5 לא נתמך מוסיף %1$s לעדכון %1$s כבר נמצא בתור העדכון קטגוריה חדשה @@ -525,4 +519,4 @@ מחק את היסטוריית הפריטים שאינם שמורים בספריה שלך מידע דיבוג פעילות רקע - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ja/plurals.xml b/i18n/src/commonMain/moko-resources/ja/plurals.xml index dfbcdc245d..ee2c5859b5 100644 --- a/i18n/src/commonMain/moko-resources/ja/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ja/plurals.xml @@ -1,101 +1,74 @@ - %1$d件のダウンロードした章を削除してもよろしいですか? - %1$s章 - - - 残り%1$dページ - - %d カテゴリー - %d件のタイトル - とさらに%1$d章 - %d件のアップデートが待機中 - %d件の拡張機能をアップデートしました - %d件の拡張機能の更新が利用可能 - %1$dページ - ソースには存在しないか、フィルターによって排除されたため、%d章がスキップされました - 次の未読の%d章 - 第%1$s章はソースから消去されています: \n%2$s \nダウンロードを削除しますか? - %1$d%2$sのシリーズを移行しますか? - %1$d%2$sのシリーズをコピーしますか? - %dのシリーズを移行しました - %1$sで完成済み %2$s件のエラーが発生しました - キャッシュをクリアしました。%dファイルが削除されました - クリーンアップ完了。%dフォルダーが消去されました - %d件のシリーズ タイプ - %d件のソース - %d件のステータス - %d件の言語 - %1$s分後 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ja/strings.xml b/i18n/src/commonMain/moko-resources/ja/strings.xml index 0f96aeb4e4..2e83cb41dd 100644 --- a/i18n/src/commonMain/moko-resources/ja/strings.xml +++ b/i18n/src/commonMain/moko-resources/ja/strings.xml @@ -1,10 +1,5 @@ - ファイルアクセス権限が必要 - 章のダウンロード、自動バックアップの作成及びローカルマンガシリーズの閲覧を行うには、TachiyomiJ2KはAndroid 11での全ファイルへのアクセス権が必要です。 -\n -\n次の画面で「全てのファイルの管理を許可」を有効にしてください。 - 章をダウンロードするには、TachiyomiJ2Kは全ファイルへのアクセス権が必要です。こちらをタップして「全てのファイルの管理を許可」を有効にしてください。 ようこそ! いくつかのデフォルトを選択しましょう。これらの設定は後から変更できます。 始める @@ -74,7 +69,6 @@ 並び順 章が見つかりません ページが見つかりません - フォーマットRARv5は未対応です 全てのダウンロードを削除しますか? 削除する章がありません ソースでの順位に基づく @@ -1075,4 +1069,4 @@ %s が予期しないエラーに遭遇しました。このメッセージをスクリーンショットにしてクラッシュログを保存し、GitHub Issue に共有することをお勧めします。 アプリを再起動 更新 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/jv/strings.xml b/i18n/src/commonMain/moko-resources/jv/strings.xml index 2729483bfb..1b251f7c71 100644 --- a/i18n/src/commonMain/moko-resources/jv/strings.xml +++ b/i18n/src/commonMain/moko-resources/jv/strings.xml @@ -1,11 +1,6 @@ - Ijin berkas dibutuhake Manga - TachiyomiJ2K mbutuhake akses menyang kabeh file ing Android 11 kanggo ndownload bab, nggawe serep otomatis, lan maca manga lokal. -\n -\n Ing layar sabanjure, aktifake \"Allow akses kanggo ngatur kabeh file.\" - TachiyomiJ2K mbutuhake akses menyang kabeh file kanggo ngundhuh bab. Tutul ing kene, banjur aktifake \"Allow akses kanggo ngatur kabeh file.\" Manhwa Judhul Bab @@ -214,4 +209,4 @@ Awas Pemasang Entri perpustakaan - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ka/strings.xml b/i18n/src/commonMain/moko-resources/ka/strings.xml index 180dc383f5..52409233d1 100644 --- a/i18n/src/commonMain/moko-resources/ka/strings.xml +++ b/i18n/src/commonMain/moko-resources/ka/strings.xml @@ -320,8 +320,7 @@ გამოსახულებსი ჩატვირთვის შეცდომა მარქაფი უკვე მიმდინარეობს გაიგეთ, რატომ - ფაილის აუცილებელი წვდომები ნაცრისფერი ბიბლიოთეკის ჩანაწერები ისტორიიდან წაიშლება ჩანაწერები, რომლებიც თქვენს ბიბლიოთეკაში არაა შენახული - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/kk/plurals.xml b/i18n/src/commonMain/moko-resources/kk/plurals.xml index 0682967128..e143e74bbf 100644 --- a/i18n/src/commonMain/moko-resources/kk/plurals.xml +++ b/i18n/src/commonMain/moko-resources/kk/plurals.xml @@ -1,48 +1,35 @@ - %d санат %d санат - %1$s минуттан кейін%1$s минуттан кейін - %1$s дегеннен кейін %2$s қателікпен орындалды %1$s дегеннен кейін %2$s қателікпен орындалды - Келесі оқылмаған тарау Келесі %d оқылмаған тарау - %1$s тарау %1$s тарау - %1$d жүктелген тарауды жою керек пе\? %1$d жүктеген тарауды жою керек пе\? - - - %1$d бет қалды - %1$d бет қалды - - Кеңейту үшін жаңарту бар%d кеңейту үшін жаңарту бар - Дереккөзі жоқ немесе сүзілген %d тарау өткізіліп жіберілді Дереккөзі жоқ немесе сүзілген %d тарау өткізіліп жіберілді - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/kk/strings.xml b/i18n/src/commonMain/moko-resources/kk/strings.xml index 86bfede22b..a3641b2c1b 100644 --- a/i18n/src/commonMain/moko-resources/kk/strings.xml +++ b/i18n/src/commonMain/moko-resources/kk/strings.xml @@ -175,10 +175,6 @@ Сұрыптау Бастау Тоқтату - Файлдарға рұқсат керек - TachiyomiJ2K тарауларды жүктеп алу, автоматты сақтық көшірме жасау және жергілікті манга оқу үшін Android 11 жүйесіндегі барлық файлдарға кіруді қажет етеді. -\n -\nКелесі экранда «Барлық файлдарды басқаруға рұқсат беру» опциясын қосыңыз. Жолақты азайтады, бірақ өнімділікке әсер етуі мүмкін Өшірулі Соңғы оқылған тарау @@ -376,7 +372,6 @@ Күйі Файл таңдайтын қолданба табылмады Сурет сақталды - RARv5 пішімі қолжетімсіз %1$d жаңарту сәтсіз өтті Тарих жойылды Жоспарланған @@ -394,7 +389,6 @@ Мұқаба жаңартылды Кітапхана жаңаруда Жүктелген бет бөлінбеді - Тараулар жүктеу үшін TachiyomiJ2K-ге барлық файлдарды қарауға рұқсат беріңіз. Мынаны басып, \"Барлық файлдарды басқаруға рұқсат ету\" дегенді қосыңыз %1$s дегенді қайда жылжыту Барлық тараулар оқылды %2$d тараудың %1$d @@ -489,4 +483,4 @@ Кітапханада жоқ жазбалардың тарихын жою Дебаг туралы ақпарат Аялық белсенділік - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/km/plurals.xml b/i18n/src/commonMain/moko-resources/km/plurals.xml index 8cbe16f088..5349e5e618 100644 --- a/i18n/src/commonMain/moko-resources/km/plurals.xml +++ b/i18n/src/commonMain/moko-resources/km/plurals.xml @@ -1,23 +1,15 @@ - %d ថ្នាក់ - លុបភាគ %1$d ដែលបានទាញយកចោល\? - %1$s ភាគ - - - នៅសល់%1$d ទំព័រទៀត - - %d ភាគបន្ទាប់ដែលមិនទាន់អាន - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/km/strings.xml b/i18n/src/commonMain/moko-resources/km/strings.xml index 841aeefe13..f4f32ec1ca 100644 --- a/i18n/src/commonMain/moko-resources/km/strings.xml +++ b/i18n/src/commonMain/moko-resources/km/strings.xml @@ -118,10 +118,6 @@ ចែករំលែក ឈប់ខ្ទាស់ មើលភាគ - ត្រូវការការអនុញ្ញាត - តាឈិយ៉ូមិJ2K ត្រូវការaccessរាល់ហ្វល់ទាំងអស់នៅក្នុងអ៊េនដ្រយដ៏១១ដើម្បីទាញយកភាគ បង្កើតbackupsដោយស្វ័យប្រវត្ត និងអានម៊េងហ្គាដែលមានស្រាប់នៅក្នុងទូរសព្ទ។ -\n -\nនៅអេក្រង់បន្ទាប់​ បើក \"អនុញ្ញាតឲ្យaccessដើម្បីគ្រប់គ្រងរាល់ហ្វាល់ទាំងអស់\" ម៉ាន់វ៉ា ម៉ាន់ហួរ កូមិច @@ -130,7 +126,6 @@ វិចិត្រករ មិនទាន់ចាប់ផ្ដើម អាន - តាឈិយ៉ូមិJ2Kត្រូវការaccessរាល់ហ្វាល់ទាំងអស់ដើម្បីទាញយកភាគ ចុចត្រង់នេះបន្ទាប់មក \"អនុញ្ញាតឲ្យaccessដើម្បីគ្រប់គ្រងរាល់ហ្វាល់ទាំងអស់\" កំពុងដំណើរការ សង្ខេបរឿង ឡើង @@ -175,7 +170,6 @@ ទម្រង់ភាគមិនត្រឹមត្រួវ កំពុងតែធ្វើបច្ចុប្បន្នភាពបណ្ណាល័យ បានជ្រើសរើស: %1$d - ទម្រង់RARv5មិនត្រូវបានទទួលយកទេ គ្មានភាគដែលឲ្យលុបបានទេ ថ្នាក់ %1$s មាននៅក្នុងជួរ @@ -242,4 +236,4 @@ តម្រៀបតម្រងឡើងវិញ មាន %d មិនទាន់អាន មើលរឿងណាមួយ - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ko/plurals.xml b/i18n/src/commonMain/moko-resources/ko/plurals.xml index 1c5e70bd8d..eca63c90d8 100644 --- a/i18n/src/commonMain/moko-resources/ko/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ko/plurals.xml @@ -1,102 +1,75 @@ - 소요 시간: %1$s, 발생한 오류: %2$s - %1$s분 후 - %d 카테고리 - %d개의 확장 앱 업데이트가 있습니다 - %d개의 제목 - %1$s장 - 다운로드한 챕터 %1$d개를 제거하시겠습니까\? - - - %1$d페이지 남음 - - 그리고 %1$d개 화 남았다 - %d 확장 앱이 업데이트됨 - %d 업데이트 보류 중 - 읽지 않은 다음 %d 회차 - 소스에 존재하지 않거나 필터링되어 있는 %d개의 회차를 건너뛰었습니다 - %1$d 페이지 - %1$s 회차가 원본에서 제거되었습소스: \n%2$s \n \n다운로드를 삭제하시겠습니까\? - %d 만화가 마이그레이션됨 - %1$d%2$s 만화를 마이그레이션하시겠습니까\? - %1$d%2$s 만화를 복사하시겠습니까\? - 캐시가 지워졌습니다. %d 파일이 삭제되었습니다 - 정리가 완료되었습니다. 제거된 %d 폴더 - %d 언어 - "%d 시리즈 형식" - %d 소스 - %d 상태 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ko/strings.xml b/i18n/src/commonMain/moko-resources/ko/strings.xml index 458b5e4257..780356ee7d 100644 --- a/i18n/src/commonMain/moko-resources/ko/strings.xml +++ b/i18n/src/commonMain/moko-resources/ko/strings.xml @@ -469,12 +469,6 @@ 읽지 않은 작품 진행 중 새 장 - 안드로이드의 파일 사용권한이 필요합니다 - 안드로이드 정책 변화에 따라 앱은 사용자에게 파일 접근권한을 요청합니다. -\nTachiyomiJ2K는 안드로이드11의 모든 파일에 접근하여, 챕터를 다운로드하고, 자동백업을 만들며, 휴대전화 저장소에 저장된 오프라인 만화를 읽을 수 있습니다. -\n -\n이에, 다음 화면에서 TachiyomiJ2K에 대한 \"모든 파일에 대한 접근권한\"을 허용해 주십시오. - TachiyomiJ2K는 챕터를 다운로드하기 위해 모든 파일에 액세스해야 합니다. 여기를 탭한 다음에 “모든 파일을 관리하기 위한 액세스 허용\"을 활성화해주십시오. 한국만화 중국만화 잠금 해제 @@ -617,7 +611,6 @@ 검색 팁이 주기적으로 표시됩니다. 추천 검색어를 길게 눌러 검색하세요. 5% 기본 사용자 에이전트 문자열 - RARv5 포맷은 지원되지 않습니다 이중 페이지 페이지 미리 로드 크기 유니폼 커버 @@ -960,4 +953,4 @@ 서재 항목 디버그 정보 백그라운드 활동 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/lt/strings.xml b/i18n/src/commonMain/moko-resources/lt/strings.xml index 5808acad7e..89e298cc8f 100644 --- a/i18n/src/commonMain/moko-resources/lt/strings.xml +++ b/i18n/src/commonMain/moko-resources/lt/strings.xml @@ -289,7 +289,6 @@ Rūšiuoti pagal Skyrių nerasta Puslapių nerasta - RARv5 formatas nepalaikomas Visada rodyti skyrių perėjimus Šoninis spaudinėjimas Nėra @@ -474,4 +473,4 @@ Bibliotekos įrašai Ištrinkite įrašų istoriją, kurie nėra išsaugoti jūsų bibliotekoje Fono veikla - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/lv/plurals.xml b/i18n/src/commonMain/moko-resources/lv/plurals.xml index 5eaa15d1b0..36325015b4 100644 --- a/i18n/src/commonMain/moko-resources/lv/plurals.xml +++ b/i18n/src/commonMain/moko-resources/lv/plurals.xml @@ -1,63 +1,48 @@ - Pabeigts %1$s ar %2$s kļūdām Pabeigts %1$s ar %2$s kļūdu Pabeigts %1$s ar %2$s kļūdām - un vēl %1$d nodaļas un vēl %1$d nodaļa un vēl %1$d nodaļas - - - Palikušas %1$d lapas - Palikusi %1$d lapa - Palikušas %1$d lapas - - %1$s nodaļas %1$s nodaļa %1$s nodaļas - Pēc %1$s minūtēmPēc %1$s minūtesPēc %1$s minūtēm - %d kategoriju%d kategorija%d kategorijas - Pieejami %d paplašinājumi atjaunināšanaiPieejams %d paplašinājums atjaunināšanaiPieejami %d paplašinājumi atjaunināšanai - Priekš %dPriekš %dPriekš %d - Izlaisti %d nodaļu, vai nu to nav avotā, vai arī tie ir izfiltrēti Izlaists %d nodaļa, vai nu tā nav avotā, vai arī tā ir izfiltrēta Izlaistas %d nodaļas, vai nu tās nav avotā, vai arī tās ir izfiltrētas - Nākamā nelasītā nodaļa Nākamā nelasītā nodaļa Nākamās %d nelasītas nodaļas - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/lv/strings.xml b/i18n/src/commonMain/moko-resources/lv/strings.xml index 500a97f5aa..cd6db93e1c 100644 --- a/i18n/src/commonMain/moko-resources/lv/strings.xml +++ b/i18n/src/commonMain/moko-resources/lv/strings.xml @@ -166,8 +166,6 @@ Nosaukums Nav sākts Komikss - - Invertētas skāriena zonas Neviens Izsekots @@ -508,7 +506,6 @@ Bibliotēka pēdējo reizi atjaunināta: %s Lejupielādēt uz priekšu Logrīks nav pieejams, ja ir iespējota lietotņu bloķēšana - RARv5 formāts netiek atbalstīts Populārs Skatiet savus nesen atjauninātos bibliotēkas ierakstus Nederīga lietotāja agent virkne @@ -532,4 +529,4 @@ Dzēst vēsturi ierakstiem, kas nav saglabāti jūsu bibliotēkā Atkļūdošanas informācija Fona darbība - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/mn/strings.xml b/i18n/src/commonMain/moko-resources/mn/strings.xml index 2ce5cb2406..01178bd9d8 100644 --- a/i18n/src/commonMain/moko-resources/mn/strings.xml +++ b/i18n/src/commonMain/moko-resources/mn/strings.xml @@ -1,12 +1,7 @@ - Файлийн зөвшөөрөл шаардлагатай Комик - \"TachiyomiJ2K\" нь Андройд 11-ийн бүх файлыг хэрэглэх зөвшөөрөл авч байж утсан дээрээс тань манга уншиж, манга татах боломжтой. -\n -\nДараа гарч ирэх хэсэгт, бүх файлийг удирдахыг зөвшөөрнө үү. Эхлээгүй - TachiyomiJ2K бүх файлуудыг ашиглах зөвшөөрөлтэй байж манга татах боломжтой. Энд дараад, бүх файлыг удирдах зөвшөөрөл олгоно уу. Манхуа Манхуа Ашиглахын тулд онгойлгоно уу @@ -45,7 +40,6 @@ Бүлгийг устгалаа. Бүлэг олдсонгүй Ямар ч бүлэг олдсонгүй - \"RARv5\" формат дэмжихгүй Бүлгийн бичиглэл буруу Эрэмбэлэх Хуудас олдсонгүй diff --git a/i18n/src/commonMain/moko-resources/ms/plurals.xml b/i18n/src/commonMain/moko-resources/ms/plurals.xml index f76c53e464..e5af82913e 100644 --- a/i18n/src/commonMain/moko-resources/ms/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ms/plurals.xml @@ -1,101 +1,74 @@ - %d kemas kini sambungan tersedia - dan %1$d lebih - Untuk %d - - - %1$d halaman yang tinggal - - Buang %1$d bab yang dimuat turun\? - Selepas %1$s - Pembersihan selesai. Di buang %d folder - Chache di bersihkan. %d fail telah di - %d manga di - Salin %1$d%2$s manga\? - Pindah %1$d%2$s manga\? - %1$s bab telah dibuang daripada sumber: \n%2$s \nPadam muat turun\? - %d kategori - Selesai dalam %1$s dengan %2$s ralat - %1$s bab - %1$d halaman - Melangkau %d bab, sama ada sumber tidak mempunyai bab tersebut, atau ia ditapis keluar - %d kemas kini belum selesai - %d sambungan dikemas kini - %d bab tidak dibaca seterusnya - %d jenis siri - %d sumber - %d status - %d bahasa - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ms/strings.xml b/i18n/src/commonMain/moko-resources/ms/strings.xml index 3e709d78c5..4b375306e2 100644 --- a/i18n/src/commonMain/moko-resources/ms/strings.xml +++ b/i18n/src/commonMain/moko-resources/ms/strings.xml @@ -706,10 +706,6 @@ Pintasan apl Tandakan julat bab sebagai belum dibaca Tandakan julat bab sebagai dibaca - TachiyomiJ2K memerlukan akses kepada semua fail di Android 11 untuk memuat turun bab, membuat sandaran automatik, dan membaca manga lokal. -\n -\nPada skrin berikutnya, hidupkan \"benarkan akses mengurus semua fail.\" - Kebenaran fail diperlukan Sumber tidak disokong Sembunyikan kandungan pemberitahuan Sesetengah pengeluar ada sekatan tambahan pada aplikasi yang akan menghentikan perkidmatan latar belakang. Laman web ini ada maklumat cara membaikinya. @@ -805,7 +801,6 @@ Zon ketik Mengikut urutan sumber Lambang bahasa - TachiyomiJ2K memerlukan akses kepada semua fail untuk memuat turun bab. Tap sini, kemudian hidupkan \"benarkan akses untuk mengurus semua fail.\" Papar garisan luar sekeliling muka hadapan Versi beta baharu tersedia! scanlator @@ -816,7 +811,6 @@ kemas kini siap 5% Untaian ejen pengguna lalai - Format RARv5 tidak disokong Tidak ditanda buku Papar %1$s Tambah label @@ -950,4 +944,4 @@ Entri pustaka Maklumat nyahpepijat Aktiviti latar belakang - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/my/plurals.xml b/i18n/src/commonMain/moko-resources/my/plurals.xml index ec069b8248..d1f94ec90e 100644 --- a/i18n/src/commonMain/moko-resources/my/plurals.xml +++ b/i18n/src/commonMain/moko-resources/my/plurals.xml @@ -1,19 +1,12 @@ - အမျိုးအစား %d ခု - - - စာမျက်နှာ %1$d ခုကျန်သေးသည် - - %1$s ပိုင်း - ဒေါင်းလုဒ်ဆွဲထားတဲ့အပိုင်း %1$d ပိုင်းကိုဖျက်ပစ်မှာလား - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/my/strings.xml b/i18n/src/commonMain/moko-resources/my/strings.xml index 809228610c..a22ace7ad5 100644 --- a/i18n/src/commonMain/moko-resources/my/strings.xml +++ b/i18n/src/commonMain/moko-resources/my/strings.xml @@ -153,7 +153,6 @@ ပြောင်းပြန်ရွေးချယ်ရန် အရံ ဒေါင်းလုဒ်အမှတ်အသားများ - ဖိုင်ခွင့်ပြုချက်လိုအပ်သည် အသစ်ထည့်ထားသော ဖျက်စရာအပိုင်းမရှိပါ ဒေါင်းထားတာတွေအားလုံးကို ဖျက်မှာလား\? @@ -173,9 +172,6 @@ … အဖြစ် ပြမည် အမျိုးအစားခုန်ကျော်သည့် ခလုတ်ကို ဖျောက်မည် နောက်ထပ် Library ဆက်တင်များ - Android 11 တွင် အပိုင်းများဒေါင်းလုဒ်လုပ်ရန်၊ အလိုအလျောက် backup လုပ်ရန်နှင့် စက်တွင်း Manga များဖတ်ရန် TachiyomiJ2K အား စက်တွင်းဖိုင်အားလုံးရယူခွင့်ပေးဖို့ လိုအပ်ပါသည်။ -\n -\nနောက်စခရင်သို့ ရောက်ရှိပါက \"ဖိုင်အားလုံးစီမံပိုင်ခွင့်\" ကို ဖွင့်ပေးပါ။ အင်တာနက်သုံး၍ အပ်ဒိတ်လုပ်ခဲ့သော ရက်စွဲအရ လေးထောင့်ကွက်ဆိုဒ် အပိုင်းအသစ်များထွက်ရှိထားပါသည် @@ -187,8 +183,6 @@ စတင်ခြင်းလမ်းညွှန် မမှန်ကန်သော အပိုင်းပုံစံဖြစ်နေသည် Filter လုပ်နေစဥ်တွင် ဗလာဖြစ်နေသည့်အမျိုးအစားများပါ ပြပေးပါ - အပိုင်းများဒေါင်းလုဒ်ဆွဲဖို့အတွက် TachiyomiJ2K အား ဖိုင်အားလုံးရယူခွင့်ပေးဖို့ လိုအပ်ပါသည်။ ဤနေရာကိုနှိပ်ပြီး \"ဖိုင်အားလုံးစီမံပိုင်ခွင့်\" ကို ဖွင့်ပေးပါ။ - RARv5 format ကို အထောက်အပံ့မပေးထားပါ သင့် filter နှင့် ကိုက်ညီသောရလဒ်မတွေ့ရှိပါ Beta ဗားရှင်းအသစ် ထွက်နေပါပြီ! အပ်ဒိတ်ကို သွင်းလို့မရခဲ့ပါ @@ -219,4 +213,4 @@ သက်တောင့်သက်သာရှိသော လေးထောင့်ပုံစံ ကျစ်ကျစ်လစ်လစ်ရှိသော လေးထောင့်ပုံစံ အပ်ဒိတ်အများအပြားလုပ်ခြင်းအားဖြင့် ဘက်ထရီသုံးစွဲမှု ပိုမိုများပြားစေသည့်အပြင် ရင်းမြစ်များကြည့်ရှုရာတွင်လည်း ပိုမိုနှေးကွေးစေနိုင်ပါသည်။ ပိုမိုသိရှိရန် ဤနေရာကိုနှိပ်ပါ။ - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/nb-rNO/plurals.xml b/i18n/src/commonMain/moko-resources/nb-rNO/plurals.xml index 3ff93df564..8672920ff1 100644 --- a/i18n/src/commonMain/moko-resources/nb-rNO/plurals.xml +++ b/i18n/src/commonMain/moko-resources/nb-rNO/plurals.xml @@ -1,46 +1,33 @@ - Utvidelsesoppdatering tilgjengelig %d utvidelsesoppdateringer tilgjengelige - Gjort på %1$s med %2$s feil Gjort på %1$s med %2$s feil - %d kategori %d kateogrier - Fjern %1$d nedlastet kapittel\? Fjern %1$d nedlastede kapitler\? - Hopper over %d kapittel, enten så mangler kilden den eller så har den blitt filtrert ut Hopper over %d kapitler, enten så mangler kilden de eller så har de blitt filtrert ut - For én tittel For %d titler - - - %1$d side igjen - %1$d sider igjen - - %1$s kapittel %1$s kapitler - kapittel har blitt fjernet fra denne kilden \n%2$s @@ -51,79 +38,64 @@ \n \nSlett nedlastningenene av dem\? - Opprenskning ferdig. Fjernet én mappe Opprenskning ferdig. Fjernet %d mapper - og %1$d kapittel til og %1$d kapitler til - Hurtiglager tømt. %d fil har blitt slettet. Hurtiglager tømt. %d filer har blitt slettet. - Etter %1$s minutt Etter %1$s minutter - %d manga flyttet %d manga flyttet - Kopier %1$d%2$s manga\? Kopier %1$d%2$s manga\? - Flytt %1$d%2$s manga\? Flytt %1$d%2$s manga\? - %1$d side %1$d sider - Utvidelse oppdatert %d utvidelser oppdatert - %d oppdatering venter %d oppdateringer venter - Neste uleste kapittel Neste %d uleste kapitler - %d språk %d språk - %d serietype %d serietyper - %d status %d statuser - %d kilde %d kilder - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml b/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml index 845c370d97..38b53fb2ba 100644 --- a/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml +++ b/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml @@ -445,7 +445,6 @@ Legg til %1$s i … Underveis Ikke startet - Filtilganger kreves Navn Ulest Avskrudd @@ -717,10 +716,6 @@ Åpne en tilfeldig serie Manhua Manhwa - TachiyomiJ2K krever tilgang til alle filer for å laste ned kapitler. Trykk her og skru på «Tillat tilgang til håndtering av alle filer.» - TachiyomiJ2K krever tilgang til alle filer i Android 11 for å laste ned kapitler, opprette automatiske sikkerhetskopier, og lese lokal manga. -\n -\nPå neste skjerm skrur du på «Innvilg tilgang til håndtering av alle filer.» Safirskumring Slett under global oppdatering, spør på kapittelsiden Fjern foreldreløse @@ -871,7 +866,6 @@ Last ned automatisk mens du leser Kunne ikke dele det nedlastede bildet Side %d ble ikke funnet under deling - RARv5-formatet støttes ikke Last ned i forkant Fungerer kun på oppføringer i biblioteket, og hvis det nåværende kapittelet samt det neste allerede er lastet ned Del opp høye bilder @@ -961,4 +955,4 @@ Programinnstillinger Debug info Bakgrunnsaktivitet - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ne/plurals.xml b/i18n/src/commonMain/moko-resources/ne/plurals.xml index a4e8ee3263..458ba1222d 100644 --- a/i18n/src/commonMain/moko-resources/ne/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ne/plurals.xml @@ -1,91 +1,69 @@ - %d वर्ग %d वर्गहरू - %1$s अध्याय %1$s अध्यायहरू - एक्सटेन्शन अपडेट उपलब्ध छ %d एक्सटेन्शन अपडेटहरू उपलब्ध छन् - अर्को नपढिएको अध्याय अर्को %d नपढिएका अध्यायहरू - %2$s त्रुटिको साथ %1$s मा सम्पन्न भयो %2$s त्रुटिहरूसँग %1$s मा सम्पन्न भयो - %1$s मिनेट पछि %1$s मिनेट पछि - %d अध्याय छोड्दै, या त स्रोत सँग छैन वा यसलाई फिल्टर गरिएको छ %d अध्यायहरू छोड्दै, या त स्रोत सँग छैन वा यसलाई फिल्टर गरिएको छ - श्रृङ्खला प्रकार %d श्रृङ्खला प्रकारहरू - स्रोत %d स्रोतहरू - भाषा %d भाषाहरू - - - %1$d पृष्ठ बाँकी छ - %1$d पृष्ठहरू बाँकी छन् - - %1$d डाउनलोड गरिएको अध्याय हटाउनुहुन्छ\? %1$d डाउनलोड गरिएका अध्यायहरू हटाउनुहुन्छ\? - %d अपडेट विचाराधीन %d अपडेटहरू विचाराधीन छन् - %d शीर्षकको लागि %d शीर्षकहरूको लागि - र थप %1$d अध्याय र थप %1$d अध्यायहरू - एक्सटेन्शन अपडेट गरियो %d एक्सटेन्शनहरू अपडेट गरियो - %1$d पृष्ठ %1$d पृष्ठहरू - स्रोतबाट एउटा अध्याय हटाइयो: \n%2$s @@ -95,34 +73,28 @@ \n \nतिनीहरूका डाउनलोडहरू हटाउने हो\? - %d श्रृङ्खला स्थानान्तरण भयो %d श्रृङ्खला स्थानान्तरण भयो - %1$d%2$s श्रृङ्खला प्रतिलिपि गर्ने हो\? %1$d%2$s श्रृङ्खला प्रतिलिपि गर्ने हो\? - %1$d%2$s श्रृङ्खला स्थानान्तरण गर्ने हो\? %1$d%2$s श्रृङ्खला स्थानान्तरण गर्ने हो\? - सफाई सक्यो। %d फोल्डर हटाइयो सफाई सक्यो। %d फोल्डरहरू हटाइयो - स्थिति %d स्थितिहरू - क्यास खाली गरियो। %d फाइल मेटाइएको छ क्यास खाली गरियो। %d फाइलहरू मेटिएका छन् - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ne/strings.xml b/i18n/src/commonMain/moko-resources/ne/strings.xml index 1481cd0dd2..99ef617e9c 100644 --- a/i18n/src/commonMain/moko-resources/ne/strings.xml +++ b/i18n/src/commonMain/moko-resources/ne/strings.xml @@ -75,7 +75,6 @@ अवैध अध्याय ढाँचा द्वारा अर्डर गर्नुहोस् अध्यायहरू भेटिएन - RARv5 समर्थित छैन अध्याय संख्या द्वारा अपलोड मिति द्वारा वर्गहरू @@ -420,8 +419,6 @@ अज्ञात त्रुटि अनपिन अध्यायहरू हेर्नुहोस् - TachiyomiJ2K लाई अध्यायहरू डाउनलोड गर्न सबै फाइलहरू माथि पहुँच चाहिन्छ। यहाँ ट्याप गर्नुहोस्, त्यसपछि \"Allow access to manage all files.\" सक्षम गर्नुहोस्। - फाइल अनुमति आवश्यक छ माङ्गा मानह्वा मानहुवा @@ -448,9 +445,6 @@ अनलक गर्न आवश्यक छ सबै डाउनलोडहरू हटाउने हो\? ब्याकअप पहिले नै प्रगतिमा छ - TachiyomiJ2K लाई अध्यायहरू डाउनलोड गर्न, स्वचालित ब्याकअपहरू सिर्जना गर्न र लोकल श्रृङ्खला पढ्नको लागि Android 11 मा सबै फाइलहरू माथि पहुँच चाहिन्छ। -\n -\nअर्को स्क्रिनमा, \"Allow access to manage all files.\" सक्षम गर्नुहोस्। ट्र्याकर पुस्तकालयमा शीर्षकहरू ट्र्याक गरिएका शीर्षकहरू @@ -962,4 +956,4 @@ एप सेटिङहरू Debug जानकारी ब्याकग्राउण्ड गतिविधि - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/nl/plurals.xml b/i18n/src/commonMain/moko-resources/nl/plurals.xml index fca879e264..712674dd35 100644 --- a/i18n/src/commonMain/moko-resources/nl/plurals.xml +++ b/i18n/src/commonMain/moko-resources/nl/plurals.xml @@ -1,66 +1,49 @@ - Extensie-update beschikbaar %d extensie-updates beschikbaar - Na %1$s minuut Na %1$s minuten - en %1$d nog hoofdstuk en %1$d nog hoofdstukken - Voor %d titel Voor %d titels - %d categorie %d categorieën - - - %1$d pagina over - %1$d paginas over - - Verwijder %1$d gedownload hoofdstuk\? Verwijder %1$d gedownloade hoofdstukken\? - Schoonmaak klaar. %d map verwijderd Schoonmaak klaar. %d mappen verwijderd - Cache geleegd. %d bestand is verwijderd Cache geleegd. %d bestanden zijn verwijderd - %d manga gemigreerd %d manga gemigreerd - %1$d%2$s manga kopiëren\? %1$d%2$s manga kopiëren\? - %1$d%2$s manga migreren\? %1$d%2$s manga migreren\? - Een hoofdstuk is verwijderd uit de bron: \n%2$s @@ -70,59 +53,48 @@ \n \nDownloads van deze hoofdstukken verwijderen\? - Voltooid in %1$s met %2$s foutmelding Voltooid in %1$s met %2$s foutmeldingen - %1$s hoofdstuk %1$s hoofdstukken - %1$d pagina %1$d pagina\'s - %d wachtende update %d wachtende updates - Extensie bijgewerkt %d extensies bijgewerkt - 1 hoofdstuk is overgeslagen, de bron mist 1 hoofdstuk of het is uitgefilterd %d hoofdstukken zijn overgeslagen, de bron mist ze of ze zijn uitgefilterd - Volgende ongelezen hoofdstuk Volgende aantal %d ongelezen hoofdstukken - %d serie types - %d bronnen - %d statussen - %d talen - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/nl/strings.xml b/i18n/src/commonMain/moko-resources/nl/strings.xml index 12acadcaff..759326871a 100644 --- a/i18n/src/commonMain/moko-resources/nl/strings.xml +++ b/i18n/src/commonMain/moko-resources/nl/strings.xml @@ -706,9 +706,6 @@ Markeer een reeks hoofstukken als gelezen Alles annuleren voor deze serie App-snelkoppelingen - TachiyomiJ2K heeft toegang nodig tot alle bestanden in Android 11 om hoofdstukken te downloaden, automatische backups aan te maken, en lokale manga te lezen. -\n -\nOp het volgende scherm, schakel \"Toegang verlenen om alle bestanden te beheren\" in. Oriëntatie Standaardoriëntatie Inbegrepen: %s @@ -742,8 +739,6 @@ Op hoofdstuknummer Op volgorde van de bron Geen bladwijzer - TachiyomiJ2K heeft toegang nodig tot alle bestanden om hoofdstukken te downloaden. Tik hier, en zet dan \"Toegang verlenen om alle bestanden te beheren.\" aan. - Bestandsrechten vereist Backup/herstellen werkt waarschijnlijk niet goed als de MIUI Optimalisatie is uitgeschakeld. Alleen via Wi-Fi Tako @@ -883,7 +878,6 @@ Lavender Voor sommige talen moet de app opnieuw worden gestart om correct weer te geven Download vooruit - RARv5-indeling wordt niet ondersteund Tik op zones Violet Standaard user agent string @@ -958,4 +952,4 @@ Bibliotheek Foutopsporingsinformatie Achtergrond activiteit - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/om/plurals.xml b/i18n/src/commonMain/moko-resources/om/plurals.xml index 98ccfd6a22..49b7188649 100644 --- a/i18n/src/commonMain/moko-resources/om/plurals.xml +++ b/i18n/src/commonMain/moko-resources/om/plurals.xml @@ -1,23 +1,15 @@ - Boqonnaa %1$d buufame haquu\? Boqonnaawwan %1$d buufaman haquu\? - Boqonnaa %1$s Boqonnaawwan %1$s - - - fuula %1$d hafe - fuulootaa %1$d hafe - - Ramaddi %d Ramaddiiwwan %d - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/om/strings.xml b/i18n/src/commonMain/moko-resources/om/strings.xml index 1c04eca211..1a19b8540b 100644 --- a/i18n/src/commonMain/moko-resources/om/strings.xml +++ b/i18n/src/commonMain/moko-resources/om/strings.xml @@ -1,6 +1,5 @@ - Hayyama faayilii barbaachisa Mangaa Hin jalqabne Adeemsa irra jira @@ -37,11 +36,7 @@ Guyyaa olkaa\'ameen Ramaddii Ramaddiiwwan - TachiyomiJ2K boqonnaawwan buusuuf, ofumaan dilbeessa uumuu fi mangaa naannoo dubbisuuf, faayiloota Android 11 keessa jiran hunda argachuu barbaachisa. -\n -\n Foddaa itti aanu irratti, \"Allow access to manage all files.\" Kan jedhuuf eeyyami . Baacoo - TachiyomiJ2K boqonnaawwan buusuuf faayiloota hunda argachuu barbaada. As tuqi, sana booda \"Allow access to manage all files\" kan jedhuuf eeyyami. Barreessaa Boqonnaawwan haaraa Maxxansi xumurame @@ -75,4 +70,4 @@ Dogoggora hin beekamne Ramaddiiwwan gulaali Ramaddii bulchuu - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pl/plurals.xml b/i18n/src/commonMain/moko-resources/pl/plurals.xml index 986a28ebe4..86bc50d7c0 100644 --- a/i18n/src/commonMain/moko-resources/pl/plurals.xml +++ b/i18n/src/commonMain/moko-resources/pl/plurals.xml @@ -1,55 +1,41 @@ - Usunąć %1$d pobrany rozdział? Usunąć %1$d pobrane rozdziały? Usunąć %1$d pobranych rozdziałów? Usunąć %1$d pobranych rozdziałów? - %1$s rozdział %1$s rozdziały %1$s rozdziałów %1$s rozdziałów - - - Pozostała %1$d strona - Pozostały %1$d strony - Pozostało %1$d stron - Pozostało %1$d stron - - %d kategoria %d kategorii %d kategorii %d kategorii - Dla %d tytułu Dla %d tytułów Dla %d tytułów Dla %d tytułów - i %1$d rozdział więcej i %1$d rozdziały więcej i %1$d rozdziałów więcej i %1$d rozdziałów więcej - Aktualizacja rozszerzenia dostępna %d aktualizacje rozszerzeń dostępne %d aktualizacji rozszerzeń dostępnych %d aktualizacji rozszerzeń dostępnych - Rozdział został usunięty ze źródła: \n%2$s @@ -67,116 +53,100 @@ \n \nUsunąć pobrane\? - Migrować %1$d%2$s mangę? Migrować %1$d%2$s mangi? Migrować %1$d%2$s mang? Migrate %1$d%2$s mang? - Skopiować %1$d%2$s mangę? Skopiować %1$d%2$s mangi? Skopiować %1$d%2$s mang? Skopiować %1$d%2$s mang? - %d manga zmigrowana %d mangi zmigrowane %d mang zmigrowanych %d mang zmigrowanych - Pamięć podręczna wyczyszczona. %d plik został usunięty Pamięć podręczna wyczyszczona. %d pliki zostały usunięty Pamięć podręczna wyczyszczona. %d plików zostało usuniętych Pamięć podręczna wyczyszczona. %d plików zostało usuniętych - Czyszczenie skończone. Usunięto %d folder Czyszczenie skończone. Usunięto %d foldery Czyszczenie skończone. Usunięto %d folderów Czyszczenie skończone. Usunięto %d folderów - Po %1$s minucie Po %1$s minutach Po %1$s minutach Po %1$s minutach - Wykonano w %1$s z %2$s błędem Wykonano w %1$s z %2$s błędami Wykonano w %1$s z %2$s błędami Wykonano w %1$s z %2$s błędami - %1$d strona %1$d strony %1$d stron %1$d stron - Rozszerzenie zaktualizowane %d rozszerzenia zaktualizowane %d rozszerzeń zaktualizowanych %d rozszerzeń zaktualizowanych - %d oczekująca aktualizacja %d oczekujące aktualizacje %d oczekujących aktualizacji %d oczekujących aktualizacji - %d brakujący rozdział %d brakujące rozdziały %d brakujących rozdziałów %d brakujących rozdziałów - Następny nieprzeczytany rozdział Następne %d nieprzeczytane rozdziały Następne %d nieprzeczytanych rozdziałów Następne %d nieprzeczytanych rozdziałów - %d źródło %d źródła %d źródeł %d źródeł - %d status %d statusy %d statusów %d statusów - %d język %d języki %d języków %d języków - %d typ serii %d typów serii %d typów serii %d typów serii - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pl/strings.xml b/i18n/src/commonMain/moko-resources/pl/strings.xml index 9ec922660a..979a0f189d 100644 --- a/i18n/src/commonMain/moko-resources/pl/strings.xml +++ b/i18n/src/commonMain/moko-resources/pl/strings.xml @@ -716,11 +716,6 @@ Źródło nie jest wspierane Funkcje kopii zapasowej mogą nie działać, jeśli optymalizacja nakładki MIUI jest wyłączona. Ukryj zawartość powiadomienia - TachiyomiJ2K wymaga dostępu do wszystkich plików w celu pobrania rozdziałów. Dotknij tutaj, a następnie włącz \"Zezwól na dostęp do wszystkich plików.\" - TachiyomiJ2K wymaga dostępu do wszystkich plików w Android 11 w celu pobierania rozdziałów, tworzenia automatycznych backupów oraz czytania lokalnie zapisanych mang. -\n -\nNa następnym ekranie włącz opcję \"Zezwól na dostęp do wszystkich plików.\" - Wymagany jest dostęp do plików Podgląd Ostatnio zainstalowane Ostatnio zaktualizowane @@ -929,7 +924,6 @@ Otwórz w aplikacji 5% Domyślny user agent string - Format RARv5 jest nieobsługiwany Poziom baterii nie jest niski Wersje beta mogą być niestabilne i mogą wymagać wyczyszczenia danych aplikacji. Fiołkowy @@ -996,4 +990,4 @@ Biblioteka Informacje debugowania Aktywność w tle - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml b/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml index 27d71c4da8..0ea1b27969 100644 --- a/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml +++ b/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml @@ -1,48 +1,35 @@ - Remover %1$d capítulo baixado\? Remover %1$d capítulos baixados\? Remover %1$d capítulos baixados\? - %1$s capítulo %1$s capítulos %1$s capítulos - - - %1$d página restante - %1$d páginas restantes - %1$d páginas restantes - - %d categoria %d categorias %d categorias - Para %d título Para %d títulos Para %d títulos - e mais %1$d capítulo e mais %1$d capítulos e mais %1$d capítulos - Atualização de extensão disponível %d atualizações de extensão disponíveis %d atualizações de extensão disponíveis - Um capítulo foi removido da fonte: \n%2$s @@ -56,100 +43,84 @@ \n \nExcluir seus downloads\? - Migrar %1$d%2$s mangá\? Migrar %1$d%2$s mangás\? Migrar %1$d%2$s mangás\? - Copiar %1$d%2$s mangá\? Copiar %1$d%2$s mangás\? Copiar %1$d%2$s mangás\? - %d mangá migrado %d mangás migrados %d mangás migrados - Cache limpo. %d arquivo foi excluído Cache limpo. %d arquivos foram excluídos Cache limpo. %d arquivos foram excluídos - Limpeza realizada. %d pasta removida Limpeza realizada. %d pastas removidas Limpeza realizada. %d pastas removidas - Após %1$s minuto Após %1$s minutos Após %1$s minutos - Concluído em %1$s com %2$s erro Concluído em %1$s com %2$s erros Concluído em %1$s com %2$s erros - %1$d página %1$d páginas %1$d páginas - %d atualização pendente %d atualizações pendentes %d atualizações pendentes - Extensão atualizada %d extensões atualizadas %d extensões atualizadas - Pulando %d capítulo, ou ele está faltando na fonte, ou ele foi filtrado Pulando %d capítulos, ou eles estão faltando na fonte, ou eles foram filtrados Pulando %d capítulos, ou eles estão faltando na fonte, ou eles foram filtrados - Próximo capítulo não lido Próximos %d capítulos não lidos Próximos %d capítulos não lidos - %d Tipo de série %d Tipos de séries %d Outras séries - %d Idioma %d Idiomas %d Outros Idiomas - %d Fonte %d Fontes %d Outras Fontes - %d estado %d estados %d outros estados - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml index 178f231dfb..3c903dc12e 100644 --- a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml +++ b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml @@ -1,6 +1,5 @@ - Bem Vindo(a)! Vamos definir algumas coisas primeiro. Você sempre pode fazer alterações nas configurações depois também. Começar @@ -17,11 +16,9 @@ Uso de bateria em plano de fundo Evite interrupções para tarefas longas como atualizações da biblioteca, downloads e restauração de backups. Conceder - Local de armazenamento não definido Local inválido: %s Local inválido - Mangás @@ -366,13 +363,11 @@ Procurar por atualizações Tela segura Segurança - Dados e armazenamento Local de armazenamento Uso de armazenamento Disponível: %1$s / Total: %2$s - Backup Criar backup @@ -428,7 +423,6 @@ Usar as últimas preferências de pré-migração salvas e fontes para migrar em massa Você também pode migrar selecionando o mangá na sua biblioteca - Repositórios de extensões Adicionar novo Repositório @@ -441,7 +435,6 @@ Substituir Assinatura já existente O repositório %1$s tem a mesma assinatura que %2$s.\nSe isso é esperado, %2$s será substituído, caso contrário entre em contato com o dono do repositório. - Versão Data de compilação @@ -797,9 +790,6 @@ Marcar um intervalo de capítulos como lido Cancelar todos para esta série Atalhos do app - TachiyomiJ2K requer acesso total ao armazenamento do Android 11 para baixar capítulos, criar backups automáticos e ler mangás locais. -\n -\nNa próxima tela ative \"Permitir acesso de gerenciamento de todos arquivos.\" Tema escuro Tema claro Não foi encontrado resultado similar @@ -808,8 +798,6 @@ Atualizações globais Abrir uma série aleatória Mostrar o número de itens - TachiyomiJ2K requer o acesso a todos os arquivos para baixar os capítulos. Toque aqui, então habilite \"Permitir o acesso para gerenciar todos os arquivos\" - Permissão para acessar arquivos Backup/restauração pode não funcionar adequadamente se a Otimização MIUI estiver desabilitada. Definir como padrão Adicionar tag @@ -996,7 +984,6 @@ Violeta 5% User Agent padrão - O formato RARv5 não é suportado Download automático durante a leitura Capítulos disponíveis offline Status @@ -1054,7 +1041,7 @@ Aplicar Informações de depuração Atividade em segundo plano - Revogar todas as extensões confiáveis + Revogar todas as extensões confiáveis Incluir configurações sensíveis (por exemplo, tokens de login de rastreadores) Falha ao adquirir acesso persistente à pasta. O aplicativo pode se comportar de forma inesperada. Erro interno: %s @@ -1079,4 +1066,4 @@ Cortar bordas (tira longa) Auto-anexar ID Abrir repositório de origem - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pt/plurals.xml b/i18n/src/commonMain/moko-resources/pt/plurals.xml index 0f14a524e8..a8e0304e51 100644 --- a/i18n/src/commonMain/moko-resources/pt/plurals.xml +++ b/i18n/src/commonMain/moko-resources/pt/plurals.xml @@ -1,89 +1,69 @@ - Atualização de extensão disponível %d atualizações de extensão disponíveis %d atualizações de extensão disponíveis - %d categoria %d categorias %d categorias - - - %1$d página restante - %1$d páginas restantes - %1$d páginas restantes - - Remover %1$d capítulo transferido\? Remover %1$d capítulos transferidos\? Remover %1$d capítulos transferidos\? - Concluído em %1$s com %2$s erro Concluído em %1$s com %2$s erros Concluído em %1$s com %2$s erros - %1$s capítulo %1$s capítulos - e mais %1$d capítulo e mais %1$d capítulos e mais %1$d capítulos - Para %d título Para %d títulos Para %d títulos - Após %1$s minuto Após %1$s minutos Após %1$s minutos - Limpeza realizada. %d pasta removida Limpeza realizada. %d pastas removidas Limpeza realizada. %d pastas removidas - Cache limpa. %d ficheiro foi apagado Cache limpa. %d ficheiros foram apagados Cache limpa. %d ficheiros foram apagados - %d manga migrada %d mangas migradas %d mangas migradas - Copiar %1$d%2$s manga\? Copiar %1$d%2$s mangas\? Copiar %1$d%2$s mangas\? - Migrar %1$d%2$s manga\? Migrar %1$d%2$s mangas\? Migrar %1$d%2$s mangas\? - Um capítulo foi removido da fonte: \n%2$s @@ -95,58 +75,49 @@ \n%2$s \nApagar as suas transferências\? - %1$d página %1$d páginas %1$d páginas - Extensão atualizada %d extensões atualizadas %d extensões atualizadas - %d atualização pendente %d atualizações pendentes %d atualizações pendentes - Ignorando %d capítulo, ou a fonte está em falta ou foi filtrado Ignorando %d capítulos, ou a fonte está em falta ou foi filtrado Capítulos %d ignorados, ou a fonte está em falta ou foi filtrado - 1 tipo de série %d tipos de séries %d tipos de séries - 1 fonte %d fontes %d fontes - 1 estado %d estados %d estados - 1 idioma %d idiomas %d idiomas - Próximo capítulo não lido Próximos %d capítulos não lidos Próximos %d capítulos não lidos - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pt/strings.xml b/i18n/src/commonMain/moko-resources/pt/strings.xml index 2e5ab32a5c..bf86fef1a1 100644 --- a/i18n/src/commonMain/moko-resources/pt/strings.xml +++ b/i18n/src/commonMain/moko-resources/pt/strings.xml @@ -787,11 +787,6 @@ Por número do capítulo Pela ordem da fonte Não foi marcado - TachiyomiJ2K requer o acesso total aos ficheiros para descarregar os capítulos. Clique aqui, então ative \"Permitir o acesso para gerir todos os ficheiros\" - TachiyomiJ2K requer acesso a todos os ficheiros no Android 11 para transferir capítulos, criar backups automáticos, e ler mangas locais. -\n -\nNo próximo ecrã, ative \"Permitir acesso de gestão de todos os ficheiros.\" - Permissões de ficheiro necessárias Isto forçará a cache transferida a recalcular. Útil se modificou transferências fora desta app e quer que a app as apanhe Usar navegação lateral Safira sombreado @@ -916,7 +911,6 @@ Melhora o desempenho do leitor Zonas de toque 5% - O formato RARv5 não é suportado Capítulos transferidos Transferência automática durante leitura Apenas funciona em entradas na biblioteca e se o capítulo atual, mais o próximo, já estiverem transferidos @@ -988,4 +982,4 @@ Permitir as notificações é recomendado para manter o aplicativo e sua biblioteca atualizados. Informações de depuração Atividade em segundo plano - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ro/plurals.xml b/i18n/src/commonMain/moko-resources/ro/plurals.xml index 5811d9d04f..4a39b90b64 100644 --- a/i18n/src/commonMain/moko-resources/ro/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ro/plurals.xml @@ -1,66 +1,50 @@ - Actualizare de extensie disponibilă %d actualizări de extensie disponibile %d actualizări de extensie disponibile - Gata în %1$s cu eroarea %2$s Gata în %1$s cu %2$s erori Gata în %1$s cu %2$s erori - %d categorie %d categorii %d categorii - - - %1$d pagină rămasă - %1$d pagini rămase - %1$d pagini rămase - - %1$s capitol %1$s capitole %1$s capitole - Eliminați %1$d capitolul descărcat\? Eliminați %1$d capitole descărcate\? Eliminați %1$d capitole descărcate\? - și %1$d capitol mai mult și %1$d mai multe capitole și %1$d mai multe capitole - Pentru %d titlu Pentru %d titluri Pentru %d titluri - Copiați %1$d%2$s manga\? Copiați %1$d%2$s manga\? Copiați %1$d%2$s manga\? - Migrează %1$d%2$s manga\? Migrează %1$d%2$s manga\? Migrează %1$d%2$s manga\? - Un capitol a fost eliminat din sursă: \n%2$s @@ -74,82 +58,69 @@ \n \nȘtergeți descărcarea acestora\? - %1$d pagină %1$d pagini %1$d pagini - Curățenie făcută. A fost eliminat %d dosar Curățenie făcută. Au fost eliminate %d dosare Curățenie făcută. Au fost eliminate %d dosare - %d manga a migrat %d mangauri au migrat %d mangauri au migrat - După %1$s minut După %1$s minute După %1$s minute - Cache curățat. %d fișierul a fost șters Cache curățat. %d fișiere au fost șterse Cache curățat. %d fișiere au fost șterse - Următorul capitol necitit Următoarele %d capitole necitite Următoarele %d capitole necitite - %d actualizare în așteptare %d actualizări în așteptare %d actualizări în așteptare - Extensia a fost actualizată Extensiile %d au fost actualizate Extensiile %d au fost actualizate - Omiterea %d capitol, fie că sursa lipsește, fie că a fost filtrată Omiterea a %d capitole, fie că sursa nu le are, fie că au fost filtrate Omiterea a %d capitole, fie că sursa nu le are, fie că au fost filtrate - %d Tip de serii %d Tipuri de serii %d Tipuri de serii - %d sursă %d surse %d surse - %d Limbă %d Limbi %d Limbi - %d status %d statusuri %d statusuri - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ro/strings.xml b/i18n/src/commonMain/moko-resources/ro/strings.xml index 1a5bf48696..5e25f5fa05 100644 --- a/i18n/src/commonMain/moko-resources/ro/strings.xml +++ b/i18n/src/commonMain/moko-resources/ro/strings.xml @@ -637,11 +637,6 @@ Aspect Sincronizare unidirecțională pentru actualizarea progresului capitolului în cadrul serviciilor de urmărire. Configurați urmărirea pentru intrările manga individuale din butonul de urmărire al acestora. Niciun capitol de șters - TachiyomiJ2K are nevoie de acces la toate fișierele pentru a descărca capitole. Atingeți aici, apoi activați \"Permiteți accesul pentru a gestiona toate fișierele\". - Permisiuni necesare pentru a accesa fișierele - TachiyomiJ2K necesită acces la toate fișierele din Android 11 pentru a descărca capitole, pentru a crea copii de rezervă automate și pentru a citi manga locală. -\n -\nÎn ecranul următor, activați \"Allow access to manage all files\" (Permiteți accesul pentru a gestiona toate fișierele). Înlăturați toate descărcările\? Editare finalizată Anulat @@ -902,7 +897,6 @@ Selectați sursele dezinstalate Link-uri utile de traducere Nu s-a putut diviza imaginea descărcată - Formatul RARv5 nu este acceptat 5% Unele limbi pot necesita o relansare a aplicației pentru a fi afișate corect Păstrează manga cu capitole citite @@ -953,4 +947,4 @@ Program instalare Intrări în bibliotecă Activități în fundal - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ru/plurals.xml b/i18n/src/commonMain/moko-resources/ru/plurals.xml index 175ad0788a..5b5e27100e 100644 --- a/i18n/src/commonMain/moko-resources/ru/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ru/plurals.xml @@ -1,90 +1,71 @@ - Кэш очищен. %d файл был удален Кэш очищен. %d файла было удалено Кэш очищен. %d файлов было удалено Кэш очищен. %d файлов было удалено - Очистка завершена. %d папка удалена Очистка завершена. %d папки удалено Очистка завершена. %d папок удалено Очистка завершена. %d папок удалено - Копировать %1$d%2$s серию\? Копировать %1$d%2$s серии\? Копировать %1$d%2$s серий\? Копировать %1$d%2$s серий\? - Мигрировать %1$d%2$s серию\? Мигрировать %1$d%2$s серии\? Мигрировать %1$d%2$s серий\? Мигрировать %1$d%2$s серий\? - Через %1$s минуту Через %1$s минуты Через %1$s минут Через %1$s минут - %d серия перемещёна %d серии перемещено %d серий перемещено %d серий перемещено - и ещё %1$d глава и ещё %1$d главы и ещё %1$d глав и ещё %1$d глав - Для %d серии Для %d серий Для %d серий Для %d серий - Удалить %1$d загруженную главу\? Удалить %1$d загруженных глав\? Удалить %1$d загруженных глав\? Удалить %1$d загруженных глав\? - %1$s глава %1$s главы %1$s глав %1$s глав - - - %1$d страница осталась - %1$d страницы осталось - %1$d страниц осталось - %1$d страниц осталось - - %d категория %d категории %d категорий %d категорий - %1$s глава была удалена из источника: \n%2$s @@ -99,81 +80,70 @@ \n%2$s \nУдалить загрузку\? - Доступно %d обновление для расширения Доступны %d обновления для расширений Доступны %d обновлений для расширений Доступны %d обновлений для расширений - Выполнено за %1$s с %2$s ошибкой Выполнено за %1$s с %2$s ошибками Выполнено за %1$s с %2$s ошибками Выполнено за %1$s с %2$s ошибками - %1$d страница %1$d страницы %1$d страниц %1$d страниц - %d обновление ожидается %d обновления ожидаются %d обновлений ожидаются %d обновлений ожидаются - Обновлено %d расширение Обновлено %d расширения Обновлено %d расширений Обновлено %d расширений - Пропущена %d глава, так как она либо отсутствует в источнике, либо была отфильтрована Пропущено %d главы, так как они либо отсутствуют в источнике, либо были отфильтрованы Пропущено %d глав, так как они либо отсутствуют в источнике, либо были отфильтрованы Пропущено %d глав, так как они либо отсутствуют в источнике, либо были отфильтрованы - Следующая непрочитанная глава Следующие %d непрочитанные главы Следующие %d непрочитанных глав Следующие %d непрочитанных глав - %d тип серии %d типа серии %d типов серий %d типов серий - %d серия %d серии %d серий %d серий - %d статус %d статуса %d статусов %d статусов - %d язык %d языка %d языков %d языков - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ru/strings.xml b/i18n/src/commonMain/moko-resources/ru/strings.xml index 287787a519..31efbdc048 100644 --- a/i18n/src/commonMain/moko-resources/ru/strings.xml +++ b/i18n/src/commonMain/moko-resources/ru/strings.xml @@ -736,9 +736,6 @@ Отметить определённое количество глав, как «Прочитано» Отменить всё для этой серии Ярлыки приложения - TachiyomiJ2K требует доступ ко всем файлам системы Android 11 для загрузки глав, создания автоматических резервных копий и чтения личных серий. -\n -\nНа следующем экране включите «Разрешить доступ к управлению всеми файлами.» Включать: %s Исключать: %s Это заставит пересчитать кэш загрузок. Полезно, если вы изменили загрузки вне этого приложения и хотите, чтобы приложение их подхватило @@ -762,8 +759,6 @@ Глобальные обновления Открыть случайную серию Показать количество серий - TachiyomiJ2K требует доступ ко всем файлам для загрузки глав. Нажмите здесь, затем включите «Разрешить доступ к управлению всеми файлами.» - Требуются права доступа к файлам Ориентация Ориентация по умолчанию Резервная копия/Восстановление может не работать должным образом, если отключена «Оптимизация MIUI». @@ -935,7 +930,6 @@ 5% User agent по умолчанию Некоторым языкам требуется перезапуск приложения для корректного отображения - Формат RARv5 не поддерживается При чтении Загруженные главы Статистика @@ -991,4 +985,4 @@ Настройки приложения Отладочная информация Фоновая активность - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sc/plurals.xml b/i18n/src/commonMain/moko-resources/sc/plurals.xml index e120eef11c..56eaabefb0 100644 --- a/i18n/src/commonMain/moko-resources/sc/plurals.xml +++ b/i18n/src/commonMain/moko-resources/sc/plurals.xml @@ -1,41 +1,33 @@ - B\'at un\'agiornamentu a disponimentu pro un\'estensione B\'ant agiornamentos a disponimentu pro %d estensiones - A pustis de %1$s minutu A pustis de %1$s minutos - Innetadura acabada. %d cartella iscantzellada Innetadura acabada. %d cartellas iscantzelladas - Memòria temporànea isboidada. %d documentu est istadu iscantzelladu Memòria temporànea isboidada. %d documentos sunt istados iscantzellados - %d manga tramudadu %d manga tramudados - Copiare %1$d%2$s manga\? Copiare %1$d%2$s manga\? - Tramudare %1$d%2$s manga\? Tramudare %1$d%2$s manga\? - Unu capìtulu est istadu bogadu dae sa mitza: \n%2$s @@ -45,84 +37,64 @@ \n \nCheres iscantzellare sos iscarrigamentos issoro\? - e %1$d àteru capìtulu e àteros %1$d capìtulos - Pro %d tìtulu Pro %d tìtulos - %d categoria %d categorias - - - Galu %1$d pàgina - Galu %1$d pàginas - - Iscantzellare %1$d capìtulu iscarrigadu\? Iscantzellare %1$d capìtulos iscarrigados\? - Fatu in %1$s cun %2$s errore Fatu in %1$s cun %2$s errores - %1$s capìtulu %1$s capìtulos - %1$d pàgina %1$d pàginas - %d agiornamentu in isetu %d agiornamentos in isetu - Estensione agiornada %d estensiones agiornadas - Brinchende %d capìtulu, sa fonte non lu tenet o est istadu bogadu cun unu filtru Brinchende %d capìtulos, sa fonte non los tenet o sunt istados bogados cun unu filtru - Su capìtulu non lèghidu imbeniente Sos %d capìtulos non lèghidos imbenientes - %d fonte %d fontes - %d istadu %d istados - %d limba %d limbas - %d casta de sèrie %d castas de sèrie - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sc/strings.xml b/i18n/src/commonMain/moko-resources/sc/strings.xml index c4ec13b5c3..00541b2482 100644 --- a/i18n/src/commonMain/moko-resources/sc/strings.xml +++ b/i18n/src/commonMain/moko-resources/sc/strings.xml @@ -729,11 +729,6 @@ Agiornamentos globales Aberi una sèrie a casu Ammustra su nùmeru de elementos - Pro iscarrigare capìtulos TachiyomiJ2K tenet bisòngiu de s\'atzessu a totu sos documentos. Incarca inoghe, e a pustis abìlita \"Permite s\'atzessu pro amministrare totu sos documentos.\" - Pro iscarrigare capìtulos, creare còpias de seguresa e lèghere manga in locale TachiyomiJ2K tenet bisòngiu de s\'atzessu a sos documentos de Android 11. -\n -\nIn s\'ischermada imbeniente abìlita \"Permite s\'atzessu pro amministrare totu sos documentos.\" - Permissos de iscritura pedidos Orientamentu Orientamentu predefinidu Sa còpia de seguresa e su riprìstinu diant pòdere non funtzionare comente si tocat si s\'otimizatzione MIUI est disabilitada. @@ -905,7 +900,6 @@ 5% Istringa de agente de utente predefinida Unas cantas limbas diant pòdere bisongiare chi torres a allùghere s\'aplicatzione pro las ammustrare comente si tocat - Su formadu RARv5 no est suportadu Mantene sos manga cun capìtulos lèghidos Totu sos manga lèghidos Iscàrriga in automàticu durante sa letura @@ -959,4 +953,4 @@ Elementos de sa biblioteca Informatziones de depuratzione de còdighe Atividade in s\'isfundu - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/si/strings.xml b/i18n/src/commonMain/moko-resources/si/strings.xml index 983a9539a9..8e95ce06da 100644 --- a/i18n/src/commonMain/moko-resources/si/strings.xml +++ b/i18n/src/commonMain/moko-resources/si/strings.xml @@ -4,7 +4,6 @@ පරිච්ඡේදය %1$s පරිච්ඡේද %2$d හි %1$d වන පරිච්ඡේදය නම - ෆයිල් සඳහා අවසරය අවශ්‍යයි මන්හ්වා (කොරියන්) මන්හුවා (චීන) කොමික් @@ -15,11 +14,7 @@ නවතා ඇත ප්‍රවේශ වීමට අගුලු හරින්න අන්ලොක් - තචියොමිJ2K සියලුම ෆයිල් සඳහා ප්‍රවේශ වීමට ඉඩ ඉල්ලයි. මෙය ස්වයංක්‍රීය බැකප් සෑදීමටත්, පරිච්ඡේද බාගැනීම් සඳහාත්, දුරකථනයේ ඇති මංගා කියවීම සඳහාත් වේ. -\n -\nඊළග තීරයේ දැක්වෙන \"allow access to manage all files\" සක්‍රීය කරන්න මංගා - තචියොමිJ2K සියලුම ෆයිල් සඳහා ප්‍රවේශ වීමට ඉඩ ඉල්ලයි. මෙය පරිච්ඡේද බාගැනීම් සඳහා වේ. මෙතන ඔබා, \"Allow access to manage all files.\" සක්‍රීය කරන්න කියවීමට පටන් අරන් නෑ චිත්‍ර ශිල්පියා කියවමින් පවතී diff --git a/i18n/src/commonMain/moko-resources/sk/plurals.xml b/i18n/src/commonMain/moko-resources/sk/plurals.xml index b739e82c7d..2397597471 100644 --- a/i18n/src/commonMain/moko-resources/sk/plurals.xml +++ b/i18n/src/commonMain/moko-resources/sk/plurals.xml @@ -1,63 +1,48 @@ - Je dostupná aktualizácia pre rozšírenie Je dostupných %d aktualizácii pre rozšírenia Sú dostupné %d aktualizácii pre rozšírenia - %d kategória %d kategórie %d kategórií - Dokončené za %1$s s %2$s chybou Dokončené za %1$s s %2$s chybami Dokončené za %1$s s %2$s chybami - Ďalšia neprečítaná kapitola Ďalšie %d neprečítané kapitoly Ďalších %d neprečítaných kapitol - %1$s kapitola %1$s kapitoly %1$s kapitol - Odstrániť %1$d stiahnutú kapitolu\? Odstrániť %1$d stiahnute kapitoly\? Odstrániť %1$d stiahnutých kapitol\? - - - Zostáva %1$d strana - Zostávú %1$d strany - Zostáva %1$d stran - - Po %1$s minúte Po %1$s minútach Po %1$s minútach - Pre %d titul Pre %d tituly Pre %d titulov - Preskočenie %d kapitoly, buď zdroj chýba, alebo bol odfiltrovaný Preskočenie %d kapitol, buď zdroj chýba, alebo bol odfiltrovaný Zložky %d kapitoly, buď zdroj chýba, alebo boli filtrované - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sk/strings.xml b/i18n/src/commonMain/moko-resources/sk/strings.xml index cd7e4bcb7b..3186c5b81d 100644 --- a/i18n/src/commonMain/moko-resources/sk/strings.xml +++ b/i18n/src/commonMain/moko-resources/sk/strings.xml @@ -288,7 +288,6 @@ Zrušiť všetko pre túto sériu Zabezpečená obrazovka Obrázok sa nepodarilo načítať - Požadované povolenia súborov Podľa dátumu nahratia Upozornenie Sledované @@ -315,10 +314,6 @@ Vždy Manhwa Komix - TachiyomiJ2K vyžaduje prístup ku všetkým súborom v Android 11 na sťahovanie mangy, vytváranie automatických záloh a čítania stiahnutej mangy. -\n -\nNa nasledujucej obrazovke, povoľte \"Povoliť prístup ku spravovaniu všetkych súborov.\" - TachiyomiJ2K vyžaduje prístup ku všetkým súborom pre stiahnutie časti. Kliknite sem, a potom potvrďte \"Povoliť prístup na spravovanie všetkých súborov\" Vzostupne Zostupne Vyberte inverzne @@ -368,7 +363,6 @@ Optimalizácia batérie je už vypnutá 10% V tvare písmena L - Formát RARv5 nie je podporovaný Žiadne kapitoly na odstránenie Zobrazenie prázdnych kategórií pri filtrovaní Žiadne zhody pre vaše filtre @@ -570,4 +564,4 @@ Inštalátor Záznamy v knižnici Aktivita na pozadí - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sq/strings.xml b/i18n/src/commonMain/moko-resources/sq/strings.xml index ebe0c9c8e3..2afffa51e4 100644 --- a/i18n/src/commonMain/moko-resources/sq/strings.xml +++ b/i18n/src/commonMain/moko-resources/sq/strings.xml @@ -266,7 +266,6 @@ Ruaje si arkiv CBZ Kapitulli i fundit Majtas - Formati RARv5 nuk mbështetet Migroni Më tepër Varg i pavlefshëm i agjentit të përdoruesit @@ -411,4 +410,4 @@ %1$d hyrje jashtë bibliotekës në bazën e të dhënave Fshi historikun për shënimet që nuk janë ruajtur në bibliotekën tënde Aktiviteti në sfond - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sr/plurals.xml b/i18n/src/commonMain/moko-resources/sr/plurals.xml index 3d081648d7..14a8db3c81 100644 --- a/i18n/src/commonMain/moko-resources/sr/plurals.xml +++ b/i18n/src/commonMain/moko-resources/sr/plurals.xml @@ -1,72 +1,55 @@ - Доступна је нова верзија екстензије %d нове верзије екстензије су доступне %d нових верзија екстензија су доступна - %d kategorija %d kategorije %d kategoriji - Прескаче се %d поглавље, или не постоји у извору или је филтером издвојено Прескаче се %d поглавља, или не постоји у извору или је филтером издвојено Прескаче се %d поглавља, или не постоји у извору или је филтером издвојено - Завршено у %1$s са %2$s грешком Завршено у %1$s са %2$s грешке Завршено у %1$s са %2$s грешака - Keš je obrisan. %d datoteka je izbrisana Keš je obrisan. %d datoteke su izbrisane Keš je obrisan. %d datoteke su izbrisane - Kopiraj %1$d%2$s mangu\? Kopiraj %1$d%2$s mange\? Koporaj %1$d%2$s mangi\? - Nakon %1$s minut Nakon %1$s minuta Nakon %1$s minuta - %d ažuriranje je na čekanju %d ažuriranja su na čekanju %d ažuriranje je na čekanju - %1$d strana %1$d strane %1$d Stranice - Čišćenje završeno.Uklonjena %d fascikla Čišćenje završeno.Uklonjene %d fascikle Čišćenje završeno.Uklonjenj %d fascikli - - - %1$d strana ostala - %1$d strana ostalo - %1$d strana ostalo - - Поглавље је уклоњено из извора: \n%2$s @@ -80,76 +63,64 @@ \n \nИзбрисати преузимања\? - %d manga prebačena %d mangi prebačeno %d mangi prebačeno - Premestite %1$d%2$s mangu\? Premestine %1$d%2$s mange\? Premestite %1$d%2$s mangi\? - %1$s poglavlje %1$s poglavlja %1$s poglavlja - Za %d naslov Za %d naslove Za %d naslovi - Još %1$d naslov Još %1$d naslova Jos %1$d naslova - Izbrisati %1$d preuzeto poglavlje\? Izbrisati %1$d preuzeta poglavlja\? Izbrisati %1$d preuzetih poglavlja\? - Екстензија ажурирана %d екстензије су ажуриране %d екстензија је ажурирано - Следеће непрочитано поглавље Следећа %d непрочитана поглавља Следећих %d непрочитаних поглавља - %d врста серије %d врсте серије %d врста серија - %d статус %d статуса %d статуса - %d извор %d извора %d извора - %d језик %d језика %d језика - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sr/strings.xml b/i18n/src/commonMain/moko-resources/sr/strings.xml index f3dfe0d43d..b3baa9fb55 100644 --- a/i18n/src/commonMain/moko-resources/sr/strings.xml +++ b/i18n/src/commonMain/moko-resources/sr/strings.xml @@ -601,7 +601,6 @@ Omogućite samo zakačene izvore za migraciju Dodatak za obaveštenje je ažuriran Upravljate onime što se preuzima - Potrebna je dozvola za datoteku Strip Nepočeto U toku @@ -671,10 +670,6 @@ Oznaceno kao pročitano Nova poglavlja Kategorija - TachiyomiJ2K je potreban pristup svim datotekama u Andorid 11 za preuzimanje poglavlja, pravljenje automatskih rezervin kopija, i čitanje lokalnih mangi. -\n -\nNa sledećem ekranu, dozvolu \"Dozvoli pristum za upravljanje svih datoteka.\" - TachiyomiJ2K traži pristum svim datotekama povodom preuzimanja ppglavlja.Pritisni ovde,a onda dozvoli \"Dozvolite pristum za upravljanje svim datotekama.\" Po redosledu izvora Kategorija sa tim imenom već postoji! Nije moguće instalirati ažuriranje @@ -903,7 +898,6 @@ Прескочи дупликатска поглавља Дозволи брисање забележених поглавља Трекери - RARv5 формат није подржан Није могуће разделити преузету слику Завршена листа Наслови на чекању @@ -958,4 +952,4 @@ Наслови колекције Информације за отклањање грешака Активност у позадини - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sv/plurals.xml b/i18n/src/commonMain/moko-resources/sv/plurals.xml index 8828125957..c8614a951f 100644 --- a/i18n/src/commonMain/moko-resources/sv/plurals.xml +++ b/i18n/src/commonMain/moko-resources/sv/plurals.xml @@ -1,46 +1,33 @@ - Tilläggsuppdatering tillgänglig %d tilläggsuppdateringar tillgängliga - Klar på %1$s med %2$s fel Klar på %1$s med %2$s fel - %d kategori %d kategorier - - - %1$d sida kvar - %1$d sidor kvar - - %1$s kapitel %1$s kapitel - Ta bort %1$d nerladdade kapitel\? Ta bort%1$d nerladdade kapitel\? - och %1$d ytterligare kapitel och %1$d ytterligare kapitel - För %d titel För %d titlar - Ett kapitel har tagits bort från källan: \n%2$s @@ -50,79 +37,64 @@ \n \nTa bort nedladdningen\? - %1$d sida %1$d sidor - Rensning klar. %d mapp har tagits bort Rensning klar. %d mappar har tagits bort - Cache rensad. %d fil har tagits bort Cache rensad. %d filer har tagits bort - %d serie migrerad %d serier migrerade - Kopiera %1$d%2$s serie\? Kopiera %1$d%2$s serier\? - Migrera %1$d%2$s serie\? Migrera %1$d%2$s serier\? - Efter %1$s minut Efter %1$s minuter - %d uppdatering väntar %d uppdateringar väntar - Tillägget uppdaterat %d tillägg uppdaterade - Hoppar över %d kapitel, antingen saknar källan det eller så har det filtrerats bort Hoppar över %d kapitel, antingen saknar källan dem eller så har de filtrerats bort - Nästa olästa kapitel Nästa %d olästa kapitel - %d typ av serie %d typer av serier - %d källa %d källor - %d status %d statusar - %d språk %d språk - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sv/strings.xml b/i18n/src/commonMain/moko-resources/sv/strings.xml index fd76e23b28..96ff1c5e1e 100644 --- a/i18n/src/commonMain/moko-resources/sv/strings.xml +++ b/i18n/src/commonMain/moko-resources/sv/strings.xml @@ -729,11 +729,6 @@ Globala uppdateringar Öppna en slumpmässig serie Visa antal artiklar - TachiyomiJ2K kräver åtkomst till alla filer för att ladda ner kapitel. Tryck här och aktivera sedan \"Tillåt åtkomst för att hantera alla filer.\" - TachiyomiJ2K kräver tillgång till alla filer i Android 11 för att kunna ladda ner kapitel, skapa automatiska säkerhetskopior, och läsa lokala serier. -\n -\nPå nästa skärm aktiverar du \"Tillåt åtkomst för att hantera alla filer\". - Filbehörigheter krävs Orientering Standardorientering Säkerhetskopiering/återställning kanske inte fungerar korrekt om MIUI-optimering är inaktiverat. @@ -904,7 +899,6 @@ Violett 5% Standardsträng för användaragent - RARv5 formatet stöds inte Nedladdade kapitel Automatisk nedladdning under läsning Statistik @@ -962,4 +956,4 @@ Appinställningar Felsökningsinformation Bakgrundsaktivitet - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/th/plurals.xml b/i18n/src/commonMain/moko-resources/th/plurals.xml index a741c0bd1e..263c7b65a9 100644 --- a/i18n/src/commonMain/moko-resources/th/plurals.xml +++ b/i18n/src/commonMain/moko-resources/th/plurals.xml @@ -1,102 +1,75 @@ - ใช้เวลาไป %1$s โดยมีข้อผิดพลาด %2$s รายการ - %d หมวดหมู่ - มีการอัปเดตส่วนขยาย %d รายการพร้อมใช้งาน - ข้ามตอนที่ %d อาจเป็นเพราะหายมาจากแหล่งที่มาหรือถูกกรองออก - โยกย้าย %1$d%2$s เรื่อง\? - คัดลอก %1$d%2$s เรื่อง\? - ล้างเสร็จสิ้น ลบออกไป %d โฟล์เดอร์ - ต้องการนำ %1$d ตอนที่ดาวน์โหลดไว้ออกหรือไม่\? - %1$s ตอน - %d เรื่องได้โยกย้ายแล้ว - ล้างแคชแล้ว แฟ้ม %d รายการได้ถูกลบ - หลังจาก %1$s นาที - ของเรื่อง %d - และอีก %1$d ตอน - อัปเดต %d รายการที่รอดำเนินการ - อัปเดตแล้ว %d ส่วนขยาย - %1$d หน้า - %1$s ตอนได้ถูกนำออกจากแหล่งที่มาแล้ว: \n%2$s \n \nต้องการลบการดาวน์โหลด\? - - - เหลือ %1$d หน้า - - %d ตอนที่ยังไม่ได้อ่านถัดไป - %d ประเภทเรื่อง - %d สถานะ - %d ภาษา - %d แหล่งที่มา - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/th/strings.xml b/i18n/src/commonMain/moko-resources/th/strings.xml index a9496a499b..32585d1d01 100644 --- a/i18n/src/commonMain/moko-resources/th/strings.xml +++ b/i18n/src/commonMain/moko-resources/th/strings.xml @@ -411,9 +411,6 @@ คู่มือเริ่มต้นใช้งาน Shizuku ไม่ทำงาน กำลังอ่าน - TachiyomiJ2K ต้องการการเข้าถึงไฟล์ทั้งหมดใน Android 11 เพื่อดาวน์โหลดตอน สร้างการสำรองข้อมูลอัตโนมัติ และอ่านซีรีย์ในเครื่อง -\n -\nในหน้าจอถัดไป ให้เปิดใช้งาน \"อนุญาตการเข้าถึงเพื่อจัดการไฟล์ทั้งหมด\" ปลดล็อก หมวดหมู่ แสดงหมวดหมู่ว่างเปล่าเมื่อกรอง @@ -679,8 +676,6 @@ ลบตอนที่นำออกแล้ว ลบตอนที่ดาวน์โหลดไว้หากแหล่งที่มาได้ลบตอนออกในออนไลน์ การลบอัตโนมัติ - จำเป็นต้องมีสิทธิ์ในการเข้าถึงไฟล์ - TachiyomiJ2K ต้องการการเข้าถึงไฟล์ทั้งหมดเพื่อดาวน์โหลดตอน แตะที่นี่ แล้วเปิดใช้งาน \"อนุญาตการเข้าถึงเพื่อจัดการไฟล์ทั้งหมด\" ยังไม่เริ่ม ไม่ระบุสถานะ ปลดล็อกเพื่อเข้าถึงคลัง @@ -905,7 +900,6 @@ 5% ตัวแทนผู้ใช้เริ่มต้น บางภาษาอาจจำเป็นต้องปิดเปิดแอปใหม่ เพื่อให้แสดงได้อย่างถูกต้อง - ไม่รับรองรูปแบบ RARv5 รายการที่อ่านหมดแล้ว ละเว้นรายการที่มีตอนที่อ่านไว้ ดาวน์โหลดอัตโนมัติขณะกำลังอ่าน @@ -962,4 +956,4 @@ การตั้งค่าแอป ข้อมูลดีบัก กิจกรรมเบื้องหลัง - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/tl/strings.xml b/i18n/src/commonMain/moko-resources/tl/strings.xml index 69eab30f00..0c8e2fa5c1 100644 --- a/i18n/src/commonMain/moko-resources/tl/strings.xml +++ b/i18n/src/commonMain/moko-resources/tl/strings.xml @@ -303,6 +303,5 @@ Linisin ang mga nadownload na kabanata Pangkalahatang Paghahanap Pangalan - Kailangan ng pahintulot sa file Tako - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/tr/plurals.xml b/i18n/src/commonMain/moko-resources/tr/plurals.xml index 34e1a5eb9a..cb82341e6a 100644 --- a/i18n/src/commonMain/moko-resources/tr/plurals.xml +++ b/i18n/src/commonMain/moko-resources/tr/plurals.xml @@ -1,76 +1,57 @@ - Uzantı güncellemesi var %d uzantı güncellemesi var - %d kategori %d kategoriler - - - %1$d sayfa kaldı - %1$d sayfa kaldı - - İndirilmiş %1$d bölüm silinsin mi\? İndirilmiş %1$d bölüm silinsin mi\? - %1$s içinde %2$s hatayla tamamlandı %1$s içinde %2$s hatayla tamamlandı - %1$d sayfa %1$d sayfa - ve %1$d bölüm daha ve %1$d bölüm daha - %d başlık için %d başlık için - %1$s bölüm %1$s bölüm - Önbellek temizlendi. %d dosya silindi Önbellek temizlendi. %d dosya silindi - %1$d%2$s manga kopyala\? %1$d%2$s manga kopyala\? - %d manga geçiş yaptı %d manga geçiş yaptı - %1$d%2$s mangayı geçiş yap\? %1$d%2$s mangayı geçiş yap\? - Temizleme bitti. %d klasörü kaldırıldı Temizleme bitti. %d klasörü kaldırıldı - Kaynaktan bir bölüm kaldırıldı: \n%2$s @@ -80,49 +61,40 @@ \n \nİndirmesi silinsin mi\? - %1$s dakika sonra %1$s dakika sonra - 1 güncelleme beklemede %d güncelleme beklemede - Uzantı güncellendi %d uzantı güncellendi - %d bölüm atlanıyor, ya kaynakta yok ya da süzgeçlenmiş %d bölüm atlanıyor, ya kaynakta yok ya da süzgeçlenmiş - Sonraki okunmayan bölüm Sonraki %d okunmayan bölüm - %d kaynak %d kaynak - %d durum %d durum - %d dil %d dil - %d seri türü %d seri türü - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/tr/strings.xml b/i18n/src/commonMain/moko-resources/tr/strings.xml index 8df178a605..169ea722a5 100644 --- a/i18n/src/commonMain/moko-resources/tr/strings.xml +++ b/i18n/src/commonMain/moko-resources/tr/strings.xml @@ -729,11 +729,6 @@ Uzantı yüklenemedi Genel güncellemeler Öge sayısını göster - TachiyomiJ2K\'in, bölümleri indirmek için tüm dosyalara erişmesi gerekir. Buraya dokunun, ardından \"Tüm dosyaları yönetmek için erişime izin ver\"i etkinleştirin - TachiyomiJ2K\'in, bölümleri indirmek, otomatik yedeklemeler oluşturmak ve yerel mangaları okumak için Android 11\'deki tüm dosyalara erişimesi gerekir. -\n -\nBir sonraki ekranda, \"Tüm dosyaları yönetmek için erişime izin ver\" seçeneğini etkinleştirin - Dosya izinleri gerekli Varsayılan yön Yön Yedekleme/geri yükleme, MIUI optimizasyonu devre dışıysa düzgün çalışmayabilir. @@ -905,7 +900,6 @@ %5 Bazı dillerin doğru görüntülenmesi için uygulamanın yeniden başlatılması gerekebilir Öntanımlı kullanıcı aracısı dizgesi - RARv5 biçimi desteklenmiyor Tüm okunan manga Okunan bölümleri olan mangaları tut Durum @@ -962,4 +956,4 @@ Arka plan etkinliği Kaynak ayarları Uygulama ayarları - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/uk/plurals.xml b/i18n/src/commonMain/moko-resources/uk/plurals.xml index d72995b822..85042abb7a 100644 --- a/i18n/src/commonMain/moko-resources/uk/plurals.xml +++ b/i18n/src/commonMain/moko-resources/uk/plurals.xml @@ -1,69 +1,53 @@ - Наявне %d оновлення для розширення %d оновлення для розширень доступні %d оновлень для розширень доступні %d оновлень для розширень доступні - - - %1$d сторінка залишилась - %1$d сторінок залишилось - %1$d сторінок залишилось - %1$d сторінок залишилось - - Видалити %1$d завантажений розділ\? Видалити %1$d завантажених розділів\? Видалити %1$d завантажених розділів\? Видалити %1$d завантажених розділів\? - Для %d заголовку Для %d заголовків Для %d заголовків Для %d заголовків - та %1$d ще розділ та %1$d розділів та %1$d розділів та %1$d розділів - Кеш очищено. %d файл було видалено Кеш очищено. %d файлів було видалено Кеш очищено. %d файлів було видалено Кеш очищено. %d файлів було видалено - %d мангу змігровано %d манґи змігровано %d манґи змігровано %d манґи змігровано - Копіювати %1$d%2$s манґу\? Копіювати %1$d%2$s манґи\? Копіювати %1$d%2$s манґи\? Копіювати %1$d%2$s манґи\? - Мігрувати %1$d%2$s манґу\? Мігрувати %1$d%2$s манґ\? Мігрувати %1$d%2$s манґ\? Мігрувати %1$d%2$s манґ\? - Розділ було видалено з джерела: \n%2$s @@ -81,102 +65,88 @@ \n \nВидалити завантажене\? - Очистку завершено. %d тека видалена Очистку завершено. %d тек видалено Очистку завершено. %d тек видалено Очистку завершено. %d тек видалено - Через %1$s хвилину Через %1$s хвилин Через %1$s хвилин Через %1$s хвилин - Зроблено за %1$s з %2$s помилкою Зроблено за %1$s з %2$s помилками Зроблено за %1$s з %2$s помилками Зроблено за %1$s з %2$s помилками - Пропускається %d розділ, тому що джерело не має його, або він був відфільтрований Пропускаються %d розділи, тому що джерело не має їх, або вони були відфільтровані Пропускання %d розділів, тому що джерело не має їх, або вони були відфільтровані Пропускання %d розділів, тому що джерело не має їх, або вони були відфільтровані - %d категорія %d категорії %d категорій %d категорій - %d Розширення оновлено %d розширення оновлені %d розширеннь оновлені %d розширеннь оновлені - %d оновлення в очікуванні %d оновлення в очікуванні %d оновлень в очікуванні %d оновлень в очікуванні - %1$s розділ %1$s розділи %1$s розділів %1$s розділів - %1$d сторінка %1$d сторінки %1$d сторінок %1$d сторінок - Наступний непрочитаний розділ Наступні %d непрочитані розділи Наступні %d непрочитані розділи Наступні %d непрочитані розділи - %d тип серії %d типи серій %d типів серій %d типів серій - %d серія %d серій %d серій %d серій - %d статус %d статусів %d статусів %d статусів - %d мова %d мов %d мов %d мов - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/uk/strings.xml b/i18n/src/commonMain/moko-resources/uk/strings.xml index 77b4bf2117..15d0549f54 100644 --- a/i18n/src/commonMain/moko-resources/uk/strings.xml +++ b/i18n/src/commonMain/moko-resources/uk/strings.xml @@ -624,12 +624,7 @@ Попередження Попередження: великий об\'єм завантажень може призвести до сповільнення роботи джерел та/або блокуванню Tachiyomi. Що 3 дні - TachiyomiJ2K потребує доступу до всіх файлів в Android 11, щоб завантажувати розділи, створювати автоматичні резервні копії та читати локальну манґу. -\n -\nНа наступному екрані ввімкніть \"Дозволити доступ для керування всіма файлами.\" - TachiyomiJ2K потребує доступу до всіх файлів для завантаження розділів. Натисніть тут, а потім увімкніть \"Дозволити доступ для керування всіма файлами.\" Нова категорія - Потрібен дозвіл доступу до файлів Не в закладинках Підказки щодо пошуку періодично з’являтимуться. Довго натисніть на пропозицію, щоб шукати це. Немає нещодавно прочитаної чи оновленої манґи @@ -904,7 +899,6 @@ Фіолетовий 5% Типовий user agent - Формат RARv5 не підтримується Завантажені розділи Автоматичне завантаження під час читання Статистика @@ -959,4 +953,4 @@ Записи бібліотеки Інформація про налагодження Фонова активність - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/vi/plurals.xml b/i18n/src/commonMain/moko-resources/vi/plurals.xml index 45e45c6456..2145ce46a3 100644 --- a/i18n/src/commonMain/moko-resources/vi/plurals.xml +++ b/i18n/src/commonMain/moko-resources/vi/plurals.xml @@ -1,101 +1,74 @@ - Có %d bản cập nhật của các tiện ích bổ sung - %d danh mục - - - còn %1$d trang - - Xóa %1$d chương đã tải\? - Hoàn tất trong %1$s với %2$s lỗi - Sau %1$s phút - Bộ nhớ đệm tạm đã được làm sạch. Tập tin %d đã được xoá - Làm sạch xong. Đã xoá %d thư mục - Đã di chuyển %d truyện - Sao chép %1$d%2$s \? - Di chuyển %1$d%2$s \? - %1$s chương đã bị xoá khỏi nguồn: \n%2$s \nXoá những gì chúng dã tải\? - %1$d trang - và %1$d chương - Cho %d tiêu đề - %1$s chương - Đã bỏ qua %d chương, vì nguồn đang bị thiếu hoặc đã bị lọc ra - %d tiện ích mở rộng đã được cập nhật - %d bản cập nhật đang chờ - %d chương chưa đọc tiếp - %d loại loạt truyện - %d ngôn ngữ - %d trạng thái - %d nguồn - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/vi/strings.xml b/i18n/src/commonMain/moko-resources/vi/strings.xml index 627acd1068..378a4ba37a 100644 --- a/i18n/src/commonMain/moko-resources/vi/strings.xml +++ b/i18n/src/commonMain/moko-resources/vi/strings.xml @@ -747,11 +747,6 @@ Hiển thị danh mục trống khi dùng bộ lọc Theo số thứ tự chương Chưa được đánh dấu - TachiyomiJ2K cần quyền truy cập tất cả các file để tải chương. Ấn vào đây, rồi xác nhận \"Cho phép quản lý tất cả các file.\" - TachiyomiJ2K cần quyền truy cập vào tất cả các tập tin trên Android 11 để tải xuống các chương, tạo bản sao lưu tự động và đọc truyện đã tải xuống. -\n -\nTrong màn hình tiếp theo, hãy cho phép \"Cho phép truy cập để quản lý tất cả tệp.\" - Yêu cầu quyền truy cập tập tin Hỗ trợ dịch thuật Mở một phần truyện ngẫu nhiên Hiển thị số truyện trong danh mục @@ -937,7 +932,6 @@ Sắp xếp bằng thời gian được nhập về 5% Chuỗi đại diện người dùng mặc định - Định dạng RARv5 không được hỗ trợ Một số ngôn ngữ sẽ cần khởi động lại ứng dụng để hiển thị chính xác Tất cả truyện đã đọc Lưu truyện đang đọc @@ -995,4 +989,4 @@ Cài đặt ứng dụng Thông tin Debug Hoạt động ngầm - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml b/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml index 59646883d2..8b43857a87 100644 --- a/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml +++ b/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml @@ -1,101 +1,72 @@ - %d 个扩展插件可更新 - %d 个类别 - 还有 %1$d 章 - 共 %d 个漫画 - - - 剩余 %1$d 页 - - 移除已下载的 %1$d 章? - - 第%1$s章已从图源中删除: -\n%2$s -\n是否删除其下载内容? + %1$s个章节在以下图源中已被删除:\n%2$s\n是否删除相应的下载内容? - %d 漫画已迁移 - 复制 %1$d%2$s 漫画? - 迁移 %1$d%2$s 漫画? - 在%1$s分钟之后 - 清除完成,移除了 %d 个目录 - 缓存已清除,%d 的文件已被删除 - 耗时 %1$s,出现 %2$s 个错误 - %1$s 章 - %1$d页 - %d 个待处理更新 - 更新了 %d 个扩展 - 跳过了 %d 章,可能是图源没有这些章节,或者被筛选规则排除了 - 下 %d 个未读章节 - %d 种连载类型 - %d 个图源 - %d 种状态 - %d 种语言 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml index f4b56102dd..639638f565 100644 --- a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml @@ -9,8 +9,8 @@ 书架 书架更新 已选择:%1$d - 扩展 - 扩展信息 + 插件 + 插件信息 筛选 已下载 已添加书签 @@ -88,7 +88,7 @@ 信任 不可信 卸载 - 不可信的插件 + 未被信任的插件 版本: %1$s 语言: %1$s 全屏 @@ -274,7 +274,7 @@ 禁用电池优化 电池优化已被关闭 全局搜索 - 自动检查扩展插件更新 + 自动检查插件更新 官网 开源许可证 电子邮件地址 @@ -508,11 +508,11 @@ 启用缩小 正在阅读 %1$s 设置为全局默认 - 扩展更新 + 插件更新 上次使用 隐藏图源 所有图源 - 搜索拓展… + 搜索插件… 在书架中 重置章节? 搜索近期… @@ -534,7 +534,7 @@ 语言 自动备份 无效的备份文件 - 此插件不是 Tachiyomi 官方插件 + 此插件不是 Tachiyomi 官方插件。 18+ 非官方 可能包含 NSFW (18+) 内容 @@ -558,14 +558,12 @@ 备份不包含任何漫画。 缺少图源: 未登录的追踪源: - 来自备份文件的数据将被还原。 -\n -\n你需要安装所有缺少的扩展,之后登录到跟踪服务以使用它们。 + 来自备份文件的数据将被还原。 \n \n你需要安装所有缺少的插件,之后登录到跟踪服务以使用它们。 已取消还原 %02d 分,%02d 秒 图源迁移指南 NSFW (18+) 图源 - 这并不能防止非官方或可能被错误标记的扩展插件在应用程序中显示 NSFW (18+) 内容。 + 这并不能防止非官方或被错误标记的插件在应用程序中显示 NSFW (18+) 内容。 未找到文件选择应用 下一个 打开日志 @@ -724,29 +722,24 @@ 浅色主题 未找到匹配项 图源不受支持 - 无法安装扩展 + 无法安装插件 全局更新 打开一个随机系列 显示项目数 - TachiyomiJ2K 需要访问所有文件才能下载章节。 点按此处,然后启用“允许访问以管理所有文件” - TachiyomiJ2K 在 Android 11 下需要授权访问所有文件才能进行下载章节、创建自动备份和阅读本地漫画的功能。 -\n -\n在接下来的出现的画面中,请启用“允许访问管理所有文件” - 需要文件权限 方向 默认方向 如果 MIUI 优化被关闭,备份/还原可能无法正常工作。 设为默认 - 要安装扩展必须禁用MIUI优化。 + 必须禁用MIUI优化才能安装插件。 按上传日期 按章节数 按图源顺序 未加为书签 首次打开时忽略刘海屏 - 更平静的你 (动态) - 更明亮的你 (动态) + 更暗淡的Material You (动态) + 更明亮的Material You (动态) 警告 不自动更新 任何网络连接 @@ -754,8 +747,8 @@ 自动更新应用 自动更新 添加标签 - 更新扩展中 - 仍然会提示先安装有些扩展。 + 更新插件中 + 某些插件可能仍然需要先安装。 更新全部 已完成更新 无法安装更新 @@ -765,13 +758,13 @@ 最近安装 最近更新 名称 - 一些扩展可能不会自动更新,如果它们是通过外部途径安装的 - 通知扩展已更新 - 自动更新扩展 + 如果插件是通过外部途径安装的,可能不会被自动更新 + 插件更新后发送通知 + 自动更新插件 拆分双页 - 扩展已更新 + 插件已更新 已安装 %1$s - 待更新扩展 + 插件待更新 影响书架网格封面 限制:%1$s 保留 %1$s 上的两个,仅本地替换 @@ -802,7 +795,7 @@ 扫译小组 错误 大型更新可能会导致电池使用量增加和图源变慢。轻按了解更多。 - 显示在图源和扩展列表中 + 显示在图源和插件列表中 显示封面图轮廓 Shizuku 未在运行 安装并启动 Shizuku 以将 Shizuku 用作插件安装程序。 @@ -894,8 +887,8 @@ 分割长图 可以改善阅读器的性能 阅读中 - 计划读 - 已完结 + 想读 + 读过 搁置中 已放弃 按获取时间排序 @@ -905,7 +898,6 @@ 5% 某些语言可能需要重启应用才能正确显示 默认 User Agent 字符串 - 不支持 RARv5 格式 所有已读漫画 保留有已读章节的漫画 统计详情 @@ -956,10 +948,105 @@ 排除的分类 安装程序 书架中的作品 - 可允许插件在不需要用户确认的情况下安装并对 Android 12 以下的设备启用自动更新 + 允许插件在不需要用户确认的情况下安装,并且在 Android 12 以下的设备上启用自动更新 应用 图源设置 应用设置 调试信息 后台活动 - + 更多设置 + 返回 + 选择文件夹 + 存储指南 + 如果从旧版更新而来、不清楚如何选择,可以查看存储指南中的从 Tachiyomi 迁移部分获得更多信息。 + 欢迎! + 首先调整一些默认设置。您也可以稍后在设置中更改它们。 + 开始使用 + 选择一个文件夹供 %1$s 存放下载的章节、备份文件等。\n\n推荐使用一个专门的文件夹。\n\n已选择文件夹:%2$s + 必要 + 可选(推荐) + 用于发送书架更新等通知。 + 后台运行权限 + 随机打开作品(全局) + 撤销所有已信任的插件? + 条漫 + 在屏幕刘海区域显示内容 + Doki + 获取永久文件存储权限失败,应用程序可能会出现异常。 + 点击这里查看 Cloudflare 帮助 + 本应用需要 WebView 才能运行 + 后退 + 安装应用权限 + 用于在更新时安装应用。 + 通知权限 + 避免需要长时间运行的书架更新、下载、备份恢复任务被中断。 + 允许 + 未设置存储位置 + 无效位置:%s + 无效位置 + 第%1$d页,共%2$d页 + 启用章节左右滑动操作 + 显示下载队列 + 长按“最近” + 长按“浏览” + 默认 + 旧版安装程序尚未实现,目前回退到 PackageInstaller(默认) + 撤销所有已信任的插件 + 打开旧版屏幕刘海设置 + 在 Android 9.0 之前的设备上,您需要在系统设置中手动调整屏幕刘海设置 + 将图片保存到单独的文件夹 + 根据作品标题创建文件夹 + 自定义校色文件 + 默认 (%d) + 存储占用 + 可用:%1$s/总共:%2$s + 包含敏感设置(例如进度记录平台的账号信息) + 上次自动备份时间:%s + 调试模式 + 扫描外部存储上的漫画 + 插件仓库 + 添加新仓库 + 添加仓库 + 仓库网址无效 + 此仓库已存在! + 您尚未添加任何仓库。 + 删除此仓库? + 确定要删除仓库“%s”吗? + 替换 + 签名密钥指纹已存在 + %1$s 仓库的签名密钥指纹与 %2$s 仓库相同。\n如果继续,%2$s 将被替换,否则请联系仓库的维护者。 + 打开图源仓库 + 无法打开网址 + 自动附件ID + 刷新 + SFW + NSFW + 内容类型 + %s 发生了意外错误。建议您截屏此消息,转储崩溃日志,然后附加到GitHub Issue中。 + 刷新 + 恶意插件可能会读取所有登录的账号或执行任意代码。\n\n信任此插件代表您愿意承担上述风险。 + 分享封面 + 更新 + 打开全局搜索 + 将详细日志打印到系统日志(会降低应用性能) + 打开最后阅读的章节 + 打开 插件/迁移 菜单 + 双击放大 + 未选中 + 已选中 + 旧版安装程序 + Webtoon + 可能会修复下载的章节同名时相互冲突的问题 + 裁剪边缘(条漫) + 自定义硬件位图阈值 + 数据与存储 + 如果阅读器加载空白图像,请逐步降低阈值。\n已选择: %s + 存储位置 + 详细日志记录 + 将作品移到底部 + 前进 + 发生意外错误 + 刚才 + 内部错误:%s + 重启应用 + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml b/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml index 0eaa5972aa..35c116d582 100644 --- a/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml +++ b/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml @@ -1,101 +1,74 @@ - %d 個擴充套件可更新 - %d 個分類 - 歷時 %1$s,出現 %2$s 個錯誤 - 略過了 %d 章,也許是來源沒有這些章節,或其已被篩選規則排除 - 共 %1$s 章 - - - 餘 %1$d 頁 - - 已清除快取,%d 個檔案已被刪除 - 移除 %1$d 個已下載的章節? - %1$s 分鐘後 - 共 %d 本漫畫 - 還有 %1$d 章 - 第%1$s章已從來源中刪除: \n%2$s \n是否刪除其下載内容? - 接下來未讀的 %d 章 - %d 個來源 - %d 種語言 - 有%d個更新正在等待 - 更新了%d個擴充套件 - 確定要遷移 %1$d%2$s 個漫畫系列嗎? - 已成功遷移 %d 個漫畫系列 - 確定要複製 %1$d%2$s 個漫畫系列嗎? - %d 系列類型 - %d 狀態 - %1$d 頁 - 清理完成。已刪除 %d 個資料夾 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml b/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml index 036ac8364a..bfb0eb968d 100644 --- a/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml @@ -453,10 +453,6 @@ 以下章節已讀 最後閱讀的章節%1$s 新章節 - 為了讓您順利下載漫畫章節、自動進行資料備份,以及閱讀儲存在本機的漫畫,TachiyomiJ2K 需要您授予其在 Android 11 環境下的「全檔案存取」權限。 -\n -\n接下來,在出現的畫面中,請啟動「允許存取並管理所有檔案」選項。 - Tachiyomi 需要有儲存空間存取權限才能下載漫畫。請允許\"管理儲存空間\" 入門指南 協助翻譯 搜尋擴充套件… @@ -574,7 +570,6 @@ 所有來源 隱藏來源 按住不放也可以重設章節歷史 - 需要檔案權限 永遠顯示目前分類 書櫃分組… 來源未安裝 @@ -674,7 +669,6 @@ 包含在全域性更新中 全域性更新 如果在這裡禁用某些按鈕,可以在其他地方找到 - 不支援 RARv5 格式 預先下載 閱讀時自動下載 已下載章數 @@ -963,4 +957,22 @@ 偵錯資訊 背景活動 分享封面 - + 更多選擇 + 返回 + 歡迎! + 先選擇一些預設值,您可以隨時在稍後的設定中更改這些東西 + 開始使用 + 打開隨機作品 + 顯示下載佇列 + 打開最後閱讀的章節 + 打開全局搜尋 + 邊緣裁剪(長圖) + 無效位置 + 已選中 + 長圖 + 重啟應用程式 + 刷新 + 預設 + 未選中 + 更新 + \ No newline at end of file From 3c00a249c303ad44e45de1e2fb4dd37f8bea5371 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 1 Jan 2025 07:44:09 +0700 Subject: [PATCH 057/181] fix: Resolve deprecated gradle function --- app/build.gradle.kts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1c987b5cc1..6b0733aeb5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,5 @@ import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsPlugin import com.google.gms.googleservices.GoogleServicesPlugin -import java.io.ByteArrayOutputStream import java.time.LocalDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter @@ -22,12 +21,8 @@ if (gradle.startParameter.taskRequests.toString().contains("standard", true)) { } fun runCommand(command: String): String { - val byteOut = ByteArrayOutputStream() - project.exec { - commandLine = command.split(" ") - standardOutput = byteOut - } - return String(byteOut.toByteArray()).trim() + val result = providers.exec { commandLine(command.split(" ")) } + return result.standardOutput.asText.get().trim() } val _versionName = "1.9.8" From b19480de1ab95584617065aa2ddbb6a4bed0d8e6 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 1 Jan 2025 07:58:43 +0700 Subject: [PATCH 058/181] chore: Remove cmake arguments --- app/build.gradle.kts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6b0733aeb5..c854c41895 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -66,11 +66,6 @@ android { //noinspection ChromeOsAbiSupport abiFilters += supportedAbis } - externalNativeBuild { - cmake { - this.arguments("-DHAVE_LIBJXL=FALSE") - } - } } splits { From 672d364f430b16a580387cb9e2fd0c7da6021c79 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 1 Jan 2025 08:03:30 +0700 Subject: [PATCH 059/181] chore: Remove stdlib --- app/build.gradle.kts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c854c41895..3ee4d61fa3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -239,8 +239,6 @@ dependencies { implementation(libs.shizuku.api) implementation(libs.shizuku.provider) - implementation(kotlin("stdlib", org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION)) - implementation(platform(kotlinx.coroutines.bom)) implementation(kotlinx.bundles.coroutines) From 54a30597304e9ea4cac63a2004d398612c15feef Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 1 Jan 2025 08:17:55 +0700 Subject: [PATCH 060/181] chore: Move core module to core.main --- app/build.gradle.kts | 2 +- core/{ => main}/build.gradle.kts | 0 core/{ => main}/src/androidMain/AndroidManifest.xml | 0 .../kanade/tachiyomi/core/preference/AndroidPreference.kt | 0 .../tachiyomi/core/preference/AndroidPreferenceStore.kt | 0 .../kotlin/eu/kanade/tachiyomi/network/AndroidCookieJar.kt | 0 .../kotlin/eu/kanade/tachiyomi/network/DohProviders.kt | 0 .../kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt | 0 .../kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt | 0 .../kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt | 0 .../eu/kanade/tachiyomi/network/ProgressResponseBody.kt | 2 +- .../kotlin/eu/kanade/tachiyomi/network/Requests.kt | 2 +- .../tachiyomi/network/interceptor/CloudflareInterceptor.kt | 0 .../tachiyomi/network/interceptor/IgnoreGzipInterceptor.kt | 0 .../tachiyomi/network/interceptor/RateLimitInterceptor.kt | 4 ++-- .../network/interceptor/SpecificHostRateLimitInterceptor.kt | 4 ++-- .../network/interceptor/UncaughtExceptionInterceptor.kt | 2 +- .../tachiyomi/network/interceptor/UserAgentInterceptor.kt | 0 .../tachiyomi/network/interceptor/WebViewInterceptor.kt | 5 +++-- .../kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt | 0 .../eu/kanade/tachiyomi/util/system/DensityExtensions.kt | 0 .../kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt | 0 .../eu/kanade/tachiyomi/util/system/ToastExtensions.kt | 1 - .../eu/kanade/tachiyomi/util/system/UniFileExtensions.kt | 1 - .../eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt | 0 .../kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt | 0 .../kotlin/yokai/core/archive/AndroidArchiveInputStream.kt | 4 ++-- .../kotlin/yokai/core/archive/AndroidArchiveReader.kt | 2 +- .../androidMain/kotlin/yokai/core/archive/ArchiveEntry.kt | 0 .../kotlin/yokai/core/archive/ArchiveInputStream.kt | 0 .../androidMain/kotlin/yokai/core/archive/ArchiveReader.kt | 0 .../src/androidMain/kotlin/yokai/core/archive/ZipWriter.kt | 4 ++-- .../androidMain/kotlin/yokai/util/lang/RxCoroutineBridge.kt | 6 +++--- .../eu/kanade/tachiyomi/core/preference/Preference.kt | 0 .../eu/kanade/tachiyomi/core/preference/PreferenceStore.kt | 0 .../kanade/tachiyomi/core/security/SecurityPreferences.kt | 0 .../eu/kanade/tachiyomi/network/NetworkPreferences.kt | 0 .../kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt | 0 .../eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt | 1 - .../eu/kanade/tachiyomi/util/system/KermitExtensions.kt | 0 .../eu/kanade/tachiyomi/core/preference/DarwinPreference.kt | 0 .../tachiyomi/core/preference/DarwinPreferenceStore.kt | 0 presentation/widget/build.gradle.kts | 2 +- settings.gradle.kts | 2 +- source/api/build.gradle.kts | 2 +- 45 files changed, 22 insertions(+), 24 deletions(-) rename core/{ => main}/build.gradle.kts (100%) rename core/{ => main}/src/androidMain/AndroidManifest.xml (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/AndroidCookieJar.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/DohProviders.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/Requests.kt (97%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/IgnoreGzipInterceptor.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt (98%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt (98%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt (97%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DensityExtensions.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/ToastExtensions.kt (94%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/UniFileExtensions.kt (97%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt (100%) rename core/{ => main}/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt (100%) rename core/{ => main}/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveInputStream.kt (100%) rename core/{ => main}/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveReader.kt (100%) rename core/{ => main}/src/androidMain/kotlin/yokai/core/archive/ArchiveEntry.kt (100%) rename core/{ => main}/src/androidMain/kotlin/yokai/core/archive/ArchiveInputStream.kt (100%) rename core/{ => main}/src/androidMain/kotlin/yokai/core/archive/ArchiveReader.kt (100%) rename core/{ => main}/src/androidMain/kotlin/yokai/core/archive/ZipWriter.kt (100%) rename core/{ => main}/src/androidMain/kotlin/yokai/util/lang/RxCoroutineBridge.kt (100%) rename core/{ => main}/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/Preference.kt (100%) rename core/{ => main}/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt (100%) rename core/{ => main}/src/commonMain/kotlin/eu/kanade/tachiyomi/core/security/SecurityPreferences.kt (100%) rename core/{ => main}/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt (100%) rename core/{ => main}/src/commonMain/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt (100%) rename core/{ => main}/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt (98%) rename core/{ => main}/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/KermitExtensions.kt (100%) rename core/{ => main}/src/iosMain/kotlin/eu/kanade/tachiyomi/core/preference/DarwinPreference.kt (100%) rename core/{ => main}/src/iosMain/kotlin/eu/kanade/tachiyomi/core/preference/DarwinPreferenceStore.kt (100%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3ee4d61fa3..1750bffd4e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -144,7 +144,7 @@ android { } dependencies { - implementation(projects.core) + implementation(projects.core.main) implementation(projects.data) implementation(projects.domain) implementation(projects.i18n) diff --git a/core/build.gradle.kts b/core/main/build.gradle.kts similarity index 100% rename from core/build.gradle.kts rename to core/main/build.gradle.kts diff --git a/core/src/androidMain/AndroidManifest.xml b/core/main/src/androidMain/AndroidManifest.xml similarity index 100% rename from core/src/androidMain/AndroidManifest.xml rename to core/main/src/androidMain/AndroidManifest.xml diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreference.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/core/preference/AndroidPreferenceStore.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/AndroidCookieJar.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/AndroidCookieJar.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/AndroidCookieJar.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/AndroidCookieJar.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/DohProviders.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/DohProviders.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/DohProviders.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/DohProviders.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt index 72248f17b7..996eba684d 100644 --- a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt +++ b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.network +import java.io.IOException import okhttp3.MediaType import okhttp3.ResponseBody import okio.Buffer @@ -7,7 +8,6 @@ import okio.BufferedSource import okio.ForwardingSource import okio.Source import okio.buffer -import java.io.IOException class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() { diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/Requests.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/Requests.kt similarity index 97% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/Requests.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/Requests.kt index dedc62fea3..c236a87960 100644 --- a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/Requests.kt +++ b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/Requests.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.network +import java.util.concurrent.TimeUnit.MINUTES import okhttp3.CacheControl import okhttp3.FormBody import okhttp3.Headers @@ -7,7 +8,6 @@ import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request import okhttp3.RequestBody -import java.util.concurrent.TimeUnit.* private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build() private val DEFAULT_HEADERS = Headers.Builder().build() diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/IgnoreGzipInterceptor.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/IgnoreGzipInterceptor.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/IgnoreGzipInterceptor.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/IgnoreGzipInterceptor.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt similarity index 98% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt index e059f2b742..301c5259bb 100644 --- a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt +++ b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt @@ -1,11 +1,11 @@ package eu.kanade.tachiyomi.network.interceptor import android.os.SystemClock +import java.io.IOException +import java.util.concurrent.TimeUnit import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response -import java.io.IOException -import java.util.concurrent.* /** * An OkHttp interceptor that handles rate limiting. diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt similarity index 98% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt index a1307de1f7..1b52525ca0 100644 --- a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt +++ b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt @@ -1,12 +1,12 @@ package eu.kanade.tachiyomi.network.interceptor import android.os.SystemClock +import java.io.IOException +import java.util.concurrent.TimeUnit import okhttp3.HttpUrl import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response -import java.io.IOException -import java.util.concurrent.* /** * An OkHttp interceptor that handles given url host's rate limiting. diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt index 1de824381b..2124e6ffd2 100644 --- a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt +++ b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.network.interceptor +import java.io.IOException import okhttp3.Interceptor import okhttp3.Response -import java.io.IOException /** * Catches any uncaught exceptions from later in the chain and rethrows as a non-fatal diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt similarity index 97% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt index 47018c7443..edfb857203 100644 --- a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt +++ b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt @@ -10,13 +10,14 @@ import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.setDefaultSettings import eu.kanade.tachiyomi.util.system.toast +import java.util.Locale +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit import okhttp3.Headers import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response import yokai.i18n.MR -import java.util.* -import java.util.concurrent.* abstract class WebViewInterceptor( private val context: Context, diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DensityExtensions.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DensityExtensions.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DensityExtensions.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DensityExtensions.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/ToastExtensions.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/ToastExtensions.kt similarity index 94% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/ToastExtensions.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/ToastExtensions.kt index 2b674180e0..d2eccb8e30 100644 --- a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/ToastExtensions.kt +++ b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/ToastExtensions.kt @@ -5,7 +5,6 @@ import android.widget.Toast import androidx.annotation.StringRes import dev.icerock.moko.resources.StringResource import yokai.util.lang.getString -import dev.icerock.moko.resources.compose.stringResource /** * Display a toast in this context. diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/UniFileExtensions.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/UniFileExtensions.kt similarity index 97% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/UniFileExtensions.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/UniFileExtensions.kt index 4c8cc9975c..ce50162b3e 100644 --- a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/UniFileExtensions.kt +++ b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/UniFileExtensions.kt @@ -7,7 +7,6 @@ import android.os.ParcelFileDescriptor import com.hippo.unifile.UniFile import java.io.BufferedOutputStream import java.io.File -import java.nio.channels.SeekableByteChannel val UniFile.nameWithoutExtension: String? get() = name?.substringBeforeLast('.') diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewClientCompat.kt diff --git a/core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt similarity index 100% rename from core/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt rename to core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/WebViewUtil.kt diff --git a/core/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveInputStream.kt b/core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveInputStream.kt similarity index 100% rename from core/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveInputStream.kt rename to core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveInputStream.kt index fb5426f46a..abe8da941b 100644 --- a/core/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveInputStream.kt +++ b/core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveInputStream.kt @@ -1,10 +1,10 @@ package yokai.core.archive +import java.nio.ByteBuffer +import kotlin.concurrent.Volatile import me.zhanghai.android.libarchive.Archive import me.zhanghai.android.libarchive.ArchiveEntry import me.zhanghai.android.libarchive.ArchiveException -import java.nio.ByteBuffer -import kotlin.concurrent.Volatile class AndroidArchiveInputStream(buffer: Long, size: Long) : ArchiveInputStream() { private val lock = Any() diff --git a/core/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveReader.kt b/core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveReader.kt similarity index 100% rename from core/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveReader.kt rename to core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveReader.kt index 8309e9b1a6..aee82a81fb 100644 --- a/core/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveReader.kt +++ b/core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveReader.kt @@ -6,8 +6,8 @@ import android.system.Os import android.system.OsConstants import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.util.system.openFileDescriptor -import me.zhanghai.android.libarchive.ArchiveException import java.io.InputStream +import me.zhanghai.android.libarchive.ArchiveException class AndroidArchiveReader(pfd: ParcelFileDescriptor) : ArchiveReader { val size = pfd.statSize diff --git a/core/src/androidMain/kotlin/yokai/core/archive/ArchiveEntry.kt b/core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveEntry.kt similarity index 100% rename from core/src/androidMain/kotlin/yokai/core/archive/ArchiveEntry.kt rename to core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveEntry.kt diff --git a/core/src/androidMain/kotlin/yokai/core/archive/ArchiveInputStream.kt b/core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveInputStream.kt similarity index 100% rename from core/src/androidMain/kotlin/yokai/core/archive/ArchiveInputStream.kt rename to core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveInputStream.kt diff --git a/core/src/androidMain/kotlin/yokai/core/archive/ArchiveReader.kt b/core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveReader.kt similarity index 100% rename from core/src/androidMain/kotlin/yokai/core/archive/ArchiveReader.kt rename to core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveReader.kt diff --git a/core/src/androidMain/kotlin/yokai/core/archive/ZipWriter.kt b/core/main/src/androidMain/kotlin/yokai/core/archive/ZipWriter.kt similarity index 100% rename from core/src/androidMain/kotlin/yokai/core/archive/ZipWriter.kt rename to core/main/src/androidMain/kotlin/yokai/core/archive/ZipWriter.kt index dd105245d6..a5d84edcfb 100644 --- a/core/src/androidMain/kotlin/yokai/core/archive/ZipWriter.kt +++ b/core/main/src/androidMain/kotlin/yokai/core/archive/ZipWriter.kt @@ -5,11 +5,11 @@ import android.system.Os import android.system.StructStat import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.util.system.openFileDescriptor +import java.io.Closeable +import java.nio.ByteBuffer import me.zhanghai.android.libarchive.Archive import me.zhanghai.android.libarchive.ArchiveEntry import me.zhanghai.android.libarchive.ArchiveException -import java.io.Closeable -import java.nio.ByteBuffer class ZipWriter(val context: Context, file: UniFile) : Closeable { private val pfd = file.openFileDescriptor(context, "wt") diff --git a/core/src/androidMain/kotlin/yokai/util/lang/RxCoroutineBridge.kt b/core/main/src/androidMain/kotlin/yokai/util/lang/RxCoroutineBridge.kt similarity index 100% rename from core/src/androidMain/kotlin/yokai/util/lang/RxCoroutineBridge.kt rename to core/main/src/androidMain/kotlin/yokai/util/lang/RxCoroutineBridge.kt index a4909a77ba..16a3dade17 100644 --- a/core/src/androidMain/kotlin/yokai/util/lang/RxCoroutineBridge.kt +++ b/core/main/src/androidMain/kotlin/yokai/util/lang/RxCoroutineBridge.kt @@ -1,5 +1,8 @@ package yokai.util.lang +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.DelicateCoroutinesApi @@ -12,9 +15,6 @@ import rx.Emitter import rx.Observable import rx.Subscriber import rx.Subscription -import kotlin.coroutines.cancellation.CancellationException -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException /* * Util functions for bridging RxJava and coroutines. Taken from TachiyomiEH/SY. diff --git a/core/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/Preference.kt b/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/Preference.kt similarity index 100% rename from core/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/Preference.kt rename to core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/Preference.kt diff --git a/core/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt b/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt similarity index 100% rename from core/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt rename to core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/core/preference/PreferenceStore.kt diff --git a/core/src/commonMain/kotlin/eu/kanade/tachiyomi/core/security/SecurityPreferences.kt b/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/core/security/SecurityPreferences.kt similarity index 100% rename from core/src/commonMain/kotlin/eu/kanade/tachiyomi/core/security/SecurityPreferences.kt rename to core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/core/security/SecurityPreferences.kt diff --git a/core/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt b/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt similarity index 100% rename from core/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt rename to core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt diff --git a/core/src/commonMain/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt b/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt similarity index 100% rename from core/src/commonMain/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt rename to core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt diff --git a/core/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt b/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt similarity index 98% rename from core/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt rename to core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt index d4989d31e9..4bb5215d2f 100644 --- a/core/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt +++ b/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/CoroutinesExtensions.kt @@ -5,7 +5,6 @@ import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.IO import kotlinx.coroutines.Job import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch diff --git a/core/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/KermitExtensions.kt b/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/KermitExtensions.kt similarity index 100% rename from core/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/KermitExtensions.kt rename to core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/util/system/KermitExtensions.kt diff --git a/core/src/iosMain/kotlin/eu/kanade/tachiyomi/core/preference/DarwinPreference.kt b/core/main/src/iosMain/kotlin/eu/kanade/tachiyomi/core/preference/DarwinPreference.kt similarity index 100% rename from core/src/iosMain/kotlin/eu/kanade/tachiyomi/core/preference/DarwinPreference.kt rename to core/main/src/iosMain/kotlin/eu/kanade/tachiyomi/core/preference/DarwinPreference.kt diff --git a/core/src/iosMain/kotlin/eu/kanade/tachiyomi/core/preference/DarwinPreferenceStore.kt b/core/main/src/iosMain/kotlin/eu/kanade/tachiyomi/core/preference/DarwinPreferenceStore.kt similarity index 100% rename from core/src/iosMain/kotlin/eu/kanade/tachiyomi/core/preference/DarwinPreferenceStore.kt rename to core/main/src/iosMain/kotlin/eu/kanade/tachiyomi/core/preference/DarwinPreferenceStore.kt diff --git a/presentation/widget/build.gradle.kts b/presentation/widget/build.gradle.kts index aa44817507..def28f0709 100644 --- a/presentation/widget/build.gradle.kts +++ b/presentation/widget/build.gradle.kts @@ -20,7 +20,7 @@ android { } dependencies { - implementation(projects.core) + implementation(projects.core.main) implementation(projects.data) implementation(projects.domain) implementation(projects.i18n) diff --git a/settings.gradle.kts b/settings.gradle.kts index 1c0f8209f0..09869b29ff 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,7 +31,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") rootProject.name = "Yokai" include(":app") -include(":core") +include(":core:main") include(":data") include(":domain") include(":i18n") diff --git a/source/api/build.gradle.kts b/source/api/build.gradle.kts index 31f3f0856e..3e4b8dac0e 100644 --- a/source/api/build.gradle.kts +++ b/source/api/build.gradle.kts @@ -21,7 +21,7 @@ kotlin { } val androidMain by getting { dependencies { - implementation(projects.core) + implementation(projects.core.main) api(androidx.preference) // Workaround for https://youtrack.jetbrains.com/issue/KT-57605 From b4377a46099167358b67fd503ab288124ddd3932 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 1 Jan 2025 09:26:15 +0700 Subject: [PATCH 061/181] refactor: Move archive related code to core.archive module --- app/build.gradle.kts | 1 + .../eu/kanade/tachiyomi/source/LocalSource.kt | 10 +++++----- .../ui/reader/loader/ChapterLoader.kt | 5 +++-- .../ui/reader/loader/DownloadPageLoader.kt | 2 +- .../ui/reader/loader/EpubPageLoader.kt | 10 ++-------- .../EpubFile.kt => yokai/util/EpubReader.kt} | 7 ++++--- core/archive/build.gradle.kts | 15 +++++++++++++++ core/archive/src/main/AndroidManifest.xml | 2 ++ .../java}/yokai/core/archive/ArchiveEntry.kt | 0 .../yokai/core/archive/ArchiveInputStream.kt} | 3 ++- .../java/yokai/core/archive/ArchiveReader.kt} | 18 +++++++----------- .../java/yokai/core/archive/EpubReader.kt} | 5 ++--- .../main/java}/yokai/core/archive/ZipWriter.kt | 2 +- .../core/archive/util/UniFileExtensions.kt | 14 ++++++++++++++ core/main/build.gradle.kts | 2 +- .../tachiyomi/util/system/UniFileExtensions.kt | 4 ---- .../yokai/core/archive/ArchiveInputStream.kt | 6 ------ .../kotlin/yokai/core/archive/ArchiveReader.kt | 9 --------- settings.gradle.kts | 1 + 19 files changed, 61 insertions(+), 55 deletions(-) rename app/src/main/java/{eu/kanade/tachiyomi/util/storage/EpubFile.kt => yokai/util/EpubReader.kt} (90%) create mode 100644 core/archive/build.gradle.kts create mode 100644 core/archive/src/main/AndroidManifest.xml rename core/{main/src/androidMain/kotlin => archive/src/main/java}/yokai/core/archive/ArchiveEntry.kt (100%) rename core/{main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveInputStream.kt => archive/src/main/java/yokai/core/archive/ArchiveInputStream.kt} (94%) rename core/{main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveReader.kt => archive/src/main/java/yokai/core/archive/ArchiveReader.kt} (55%) rename core/{main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt => archive/src/main/java/yokai/core/archive/EpubReader.kt} (96%) rename core/{main/src/androidMain/kotlin => archive/src/main/java}/yokai/core/archive/ZipWriter.kt (97%) create mode 100644 core/archive/src/main/java/yokai/core/archive/util/UniFileExtensions.kt delete mode 100644 core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveInputStream.kt delete mode 100644 core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveReader.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1750bffd4e..75a95ee35e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -144,6 +144,7 @@ android { } dependencies { + implementation(projects.core.archive) implementation(projects.core.main) implementation(projects.data) implementation(projects.domain) diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt index f7040a5c66..e8e9510f61 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt @@ -12,8 +12,6 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.storage.DiskUtil -import eu.kanade.tachiyomi.util.storage.EpubFile -import eu.kanade.tachiyomi.util.storage.fillMetadata import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.extension import eu.kanade.tachiyomi.util.system.nameWithoutExtension @@ -33,7 +31,8 @@ import nl.adaptivity.xmlutil.serialization.XML import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import yokai.core.archive.archiveReader +import yokai.core.archive.util.archiveReader +import yokai.core.archive.util.epubReader import yokai.core.metadata.COMIC_INFO_FILE import yokai.core.metadata.ComicInfo import yokai.core.metadata.copyFromComicInfo @@ -42,6 +41,7 @@ import yokai.domain.chapter.services.ChapterRecognition import yokai.domain.source.SourcePreferences import yokai.domain.storage.StorageManager import yokai.i18n.MR +import yokai.util.fillMetadata import yokai.util.lang.getString class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSource { @@ -410,7 +410,7 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour } } is Format.Epub -> { - EpubFile(format.file.archiveReader(context)).use { epub -> + format.file.epubReader(context).use { epub -> val entry = epub.getImagesFromPages().firstOrNull() entry?.let { updateCover(manga, epub.getInputStream(it)!!, context) } @@ -433,7 +433,7 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour true } is Format.Epub -> { - EpubFile(format.file.archiveReader(context)).use { epub -> + format.file.epubReader(context).use { epub -> epub.fillMetadata(chapter, manga) } true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt index 7756cdd585..44f89294e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt @@ -10,7 +10,8 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.util.system.withIOContext -import yokai.core.archive.archiveReader +import yokai.core.archive.util.archiveReader +import yokai.core.archive.util.epubReader import yokai.i18n.MR import yokai.util.lang.getString @@ -82,7 +83,7 @@ class ChapterLoader( when (format) { is LocalSource.Format.Directory -> DirectoryPageLoader(format.file) is LocalSource.Format.Archive -> ArchivePageLoader(format.file.archiveReader(context)) - is LocalSource.Format.Epub -> EpubPageLoader(format.file.archiveReader(context)) + is LocalSource.Format.Epub -> EpubPageLoader(format.file.epubReader(context)) } } else -> error(context.getString(MR.strings.source_not_installed)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt index 6535ce9847..254db17580 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt @@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import uy.kohesive.injekt.injectLazy -import yokai.core.archive.archiveReader +import yokai.core.archive.util.archiveReader /** * Loader used to load a chapter from the downloaded chapters. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt index 0823453d1c..77c782e87e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt @@ -2,21 +2,15 @@ package eu.kanade.tachiyomi.ui.reader.loader import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage -import eu.kanade.tachiyomi.util.storage.EpubFile -import yokai.core.archive.ArchiveReader +import yokai.core.archive.EpubReader /** * Loader used to load a chapter from a .epub file. */ -class EpubPageLoader(reader: ArchiveReader) : PageLoader() { +class EpubPageLoader(private val epub: EpubReader) : PageLoader() { override val isLocal: Boolean = true - /** - * The epub file. - */ - private val epub = EpubFile(reader) - /** * Recycles this loader and the open zip. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt b/app/src/main/java/yokai/util/EpubReader.kt similarity index 90% rename from app/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt rename to app/src/main/java/yokai/util/EpubReader.kt index 54c9a3ebd6..2c0869aff4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt +++ b/app/src/main/java/yokai/util/EpubReader.kt @@ -1,15 +1,16 @@ -package eu.kanade.tachiyomi.util.storage +package yokai.util import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import java.text.ParseException import java.text.SimpleDateFormat -import java.util.* +import java.util.Locale +import yokai.core.archive.EpubReader /** * Fills manga and chapter metadata using this epub file's metadata. */ -fun EpubFile.fillMetadata(chapter: SChapter, manga: SManga) { +fun EpubReader.fillMetadata(chapter: SChapter, manga: SManga) { val ref = getPackageHref() val doc = getPackageDocument(ref) diff --git a/core/archive/build.gradle.kts b/core/archive/build.gradle.kts new file mode 100644 index 0000000000..e44e620ed5 --- /dev/null +++ b/core/archive/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("yokai.android.library") + kotlin("android") + alias(kotlinx.plugins.serialization) +} + +android { + namespace = "yokai.core.archive" +} + +dependencies { + implementation(libs.jsoup) + implementation(libs.libarchive) + implementation(libs.unifile) +} diff --git a/core/archive/src/main/AndroidManifest.xml b/core/archive/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..8072ee00db --- /dev/null +++ b/core/archive/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveEntry.kt b/core/archive/src/main/java/yokai/core/archive/ArchiveEntry.kt similarity index 100% rename from core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveEntry.kt rename to core/archive/src/main/java/yokai/core/archive/ArchiveEntry.kt diff --git a/core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveInputStream.kt b/core/archive/src/main/java/yokai/core/archive/ArchiveInputStream.kt similarity index 94% rename from core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveInputStream.kt rename to core/archive/src/main/java/yokai/core/archive/ArchiveInputStream.kt index abe8da941b..5cd17aa026 100644 --- a/core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveInputStream.kt +++ b/core/archive/src/main/java/yokai/core/archive/ArchiveInputStream.kt @@ -1,12 +1,13 @@ package yokai.core.archive +import java.io.InputStream import java.nio.ByteBuffer import kotlin.concurrent.Volatile import me.zhanghai.android.libarchive.Archive import me.zhanghai.android.libarchive.ArchiveEntry import me.zhanghai.android.libarchive.ArchiveException -class AndroidArchiveInputStream(buffer: Long, size: Long) : ArchiveInputStream() { +class ArchiveInputStream(buffer: Long, size: Long) : InputStream() { private val lock = Any() @Volatile private var isClosed = false diff --git a/core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveReader.kt b/core/archive/src/main/java/yokai/core/archive/ArchiveReader.kt similarity index 55% rename from core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveReader.kt rename to core/archive/src/main/java/yokai/core/archive/ArchiveReader.kt index aee82a81fb..97584cc08f 100644 --- a/core/main/src/androidMain/kotlin/yokai/core/archive/AndroidArchiveReader.kt +++ b/core/archive/src/main/java/yokai/core/archive/ArchiveReader.kt @@ -1,23 +1,22 @@ package yokai.core.archive -import android.content.Context import android.os.ParcelFileDescriptor import android.system.Os import android.system.OsConstants -import com.hippo.unifile.UniFile -import eu.kanade.tachiyomi.util.system.openFileDescriptor +import java.io.Closeable import java.io.InputStream import me.zhanghai.android.libarchive.ArchiveException -class AndroidArchiveReader(pfd: ParcelFileDescriptor) : ArchiveReader { +class ArchiveReader(pfd: ParcelFileDescriptor) : Closeable { val size = pfd.statSize val address = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, pfd.fileDescriptor, 0) - override fun useEntries(block: (Sequence) -> T): T = - AndroidArchiveInputStream(address, size).use { block(generateSequence { it.getNextEntry() }) } + fun useEntries(block: (Sequence) -> T): T = ArchiveInputStream(address, size).use { + block(generateSequence { it.getNextEntry() }) + } - override fun getInputStream(entryName: String): InputStream? { - val archive = AndroidArchiveInputStream(address, size) + fun getInputStream(entryName: String): InputStream? { + val archive = ArchiveInputStream(address, size) try { while (true) { val entry = archive.getNextEntry() ?: break @@ -37,6 +36,3 @@ class AndroidArchiveReader(pfd: ParcelFileDescriptor) : ArchiveReader { Os.munmap(address, size) } } - -fun UniFile.archiveReader(context: Context): ArchiveReader = - openFileDescriptor(context, "r").use { AndroidArchiveReader(it) } diff --git a/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt b/core/archive/src/main/java/yokai/core/archive/EpubReader.kt similarity index 96% rename from core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt rename to core/archive/src/main/java/yokai/core/archive/EpubReader.kt index b1a2711749..89ba98096b 100644 --- a/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt +++ b/core/archive/src/main/java/yokai/core/archive/EpubReader.kt @@ -1,16 +1,15 @@ -package eu.kanade.tachiyomi.util.storage +package yokai.core.archive import java.io.Closeable import java.io.File import java.io.InputStream import org.jsoup.Jsoup import org.jsoup.nodes.Document -import yokai.core.archive.ArchiveReader /** * Wrapper over ZipFile to load files in epub format. */ -class EpubFile(private val reader: ArchiveReader) : Closeable by reader { +class EpubReader(private val reader: ArchiveReader) : Closeable by reader { /** * Path separator used by this epub. diff --git a/core/main/src/androidMain/kotlin/yokai/core/archive/ZipWriter.kt b/core/archive/src/main/java/yokai/core/archive/ZipWriter.kt similarity index 97% rename from core/main/src/androidMain/kotlin/yokai/core/archive/ZipWriter.kt rename to core/archive/src/main/java/yokai/core/archive/ZipWriter.kt index a5d84edcfb..3ee02b02b7 100644 --- a/core/main/src/androidMain/kotlin/yokai/core/archive/ZipWriter.kt +++ b/core/archive/src/main/java/yokai/core/archive/ZipWriter.kt @@ -4,12 +4,12 @@ import android.content.Context import android.system.Os import android.system.StructStat import com.hippo.unifile.UniFile -import eu.kanade.tachiyomi.util.system.openFileDescriptor import java.io.Closeable import java.nio.ByteBuffer import me.zhanghai.android.libarchive.Archive import me.zhanghai.android.libarchive.ArchiveEntry import me.zhanghai.android.libarchive.ArchiveException +import yokai.core.archive.util.openFileDescriptor class ZipWriter(val context: Context, file: UniFile) : Closeable { private val pfd = file.openFileDescriptor(context, "wt") diff --git a/core/archive/src/main/java/yokai/core/archive/util/UniFileExtensions.kt b/core/archive/src/main/java/yokai/core/archive/util/UniFileExtensions.kt new file mode 100644 index 0000000000..588f8fcc81 --- /dev/null +++ b/core/archive/src/main/java/yokai/core/archive/util/UniFileExtensions.kt @@ -0,0 +1,14 @@ +package yokai.core.archive.util + +import android.content.Context +import android.os.ParcelFileDescriptor +import com.hippo.unifile.UniFile +import yokai.core.archive.ArchiveReader +import yokai.core.archive.EpubReader + +fun UniFile.openFileDescriptor(context: Context, mode: String): ParcelFileDescriptor = + context.contentResolver.openFileDescriptor(uri, mode) ?: error("Failed to open file descriptor: ${filePath ?: uri.toString()}") + +fun UniFile.archiveReader(context: Context): ArchiveReader = openFileDescriptor(context, "r").use { ArchiveReader(it) } + +fun UniFile.epubReader(context: Context): EpubReader = EpubReader(archiveReader(context)) diff --git a/core/main/build.gradle.kts b/core/main/build.gradle.kts index d78b8e4392..db1374c6ab 100644 --- a/core/main/build.gradle.kts +++ b/core/main/build.gradle.kts @@ -60,7 +60,7 @@ kotlin { } android { - namespace = "yokai.core" + namespace = "yokai.core.main" } tasks { diff --git a/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/UniFileExtensions.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/UniFileExtensions.kt index ce50162b3e..1e55656c32 100644 --- a/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/UniFileExtensions.kt +++ b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/UniFileExtensions.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.util.system import android.content.Context import android.os.Build import android.os.FileUtils -import android.os.ParcelFileDescriptor import com.hippo.unifile.UniFile import java.io.BufferedOutputStream import java.io.File @@ -48,6 +47,3 @@ fun UniFile.writeText(string: String, onComplete: () -> Unit = {}) { onComplete() } } - -fun UniFile.openFileDescriptor(context: Context, mode: String): ParcelFileDescriptor = - context.contentResolver.openFileDescriptor(uri, mode) ?: error("Failed to open file descriptor: $displayablePath") diff --git a/core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveInputStream.kt b/core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveInputStream.kt deleted file mode 100644 index 6a9cd0185b..0000000000 --- a/core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveInputStream.kt +++ /dev/null @@ -1,6 +0,0 @@ -package yokai.core.archive - -import java.io.InputStream - -// TODO: Use Okio's Source -abstract class ArchiveInputStream : InputStream() diff --git a/core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveReader.kt b/core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveReader.kt deleted file mode 100644 index 00d646f3a7..0000000000 --- a/core/main/src/androidMain/kotlin/yokai/core/archive/ArchiveReader.kt +++ /dev/null @@ -1,9 +0,0 @@ -package yokai.core.archive - -import java.io.Closeable -import java.io.InputStream - -interface ArchiveReader : Closeable { - fun useEntries(block: (Sequence) -> T): T - fun getInputStream(entryName: String): InputStream? -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 09869b29ff..bd2a49e70e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,6 +31,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") rootProject.name = "Yokai" include(":app") +include(":core:archive") include(":core:main") include(":data") include(":domain") From 2cf2fcfc4f85dc9e0b4d3e3abe304a03df3c4353 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 1 Jan 2025 10:28:00 +0700 Subject: [PATCH 062/181] refactor: WIP delegated source refactor J2K only handles deep link, which was disabled when I forked it as Yokai... Might gonna re-introduce it for some sources I used later (mainly Cubari tbh) --- .../kanade/tachiyomi/source/SourceManager.kt | 40 +- .../source/online/DelegatedHttpSource.kt | 47 --- .../tachiyomi/source/online/all/Cubari.kt | 29 +- .../tachiyomi/source/online/all/MangaDex.kt | 32 +- .../source/online/english/FoolSlide.kt | 110 ----- .../source/online/english/KireiCake.kt | 28 -- .../source/online/english/MangaPlus.kt | 41 +- .../tachiyomi/ui/reader/ReaderViewModel.kt | 10 +- .../source/online/DelegatedHttpSource.kt | 378 ++++++++++++++++++ .../tachiyomi/source/online/HttpSource.kt | 20 +- 10 files changed, 460 insertions(+), 275 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/online/DelegatedHttpSource.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/online/english/FoolSlide.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/source/online/english/KireiCake.kt create mode 100644 source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/DelegatedHttpSource.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt index 407aa4a491..b503491fc7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt @@ -1,20 +1,13 @@ package eu.kanade.tachiyomi.source import android.content.Context -import eu.kanade.tachiyomi.R -import yokai.i18n.MR -import yokai.util.lang.getString -import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.DelegatedHttpSource import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.source.online.all.Cubari -import eu.kanade.tachiyomi.source.online.all.MangaDex -import eu.kanade.tachiyomi.source.online.english.KireiCake -import eu.kanade.tachiyomi.source.online.english.MangaPlus +import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -24,7 +17,8 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import java.util.concurrent.ConcurrentHashMap +import yokai.i18n.MR +import yokai.util.lang.getString class SourceManager( private val context: Context, @@ -40,28 +34,8 @@ class SourceManager( val catalogueSources: Flow> = sourcesMapFlow.map { it.values.filterIsInstance() } val onlineSources: Flow> = catalogueSources.map { it.filterIsInstance() } - private val delegatedSources = listOf( - DelegatedSource( - "reader.kireicake.com", - 5509224355268673176, - KireiCake(), - ), - DelegatedSource( - "mangadex.org", - 2499283573021220255, - MangaDex(), - ), - DelegatedSource( - "mangaplus.shueisha.co.jp", - 1998944621602463790, - MangaPlus(), - ), - DelegatedSource( - "cubari.moe", - 6338219619148105941, - Cubari(), - ), - ).associateBy { it.sourceId } + // FIXME: Delegated source, unused at the moment, J2K only delegate deep links + private val delegatedSources = emptyList().associateBy { it.sourceId } init { scope.launch { @@ -71,8 +45,8 @@ class SourceManager( extensions.forEach { extension -> extension.sources.forEach { mutableMap[it.id] = it - delegatedSources[it.id]?.delegatedHttpSource?.delegate = it as? HttpSource -// registerStubSource(it) + //delegatedSources[it.id]?.delegatedHttpSource?.delegate = it as? HttpSource + //registerStubSource(it) } } sourcesMapFlow.value = mutableMap diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/DelegatedHttpSource.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/DelegatedHttpSource.kt deleted file mode 100644 index 1680498ce0..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/DelegatedHttpSource.kt +++ /dev/null @@ -1,47 +0,0 @@ -package eu.kanade.tachiyomi.source.online - -import android.net.Uri -import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.database.models.MangaImpl -import eu.kanade.tachiyomi.data.database.models.create -import eu.kanade.tachiyomi.domain.manga.models.Manga -import eu.kanade.tachiyomi.network.NetworkHelper -import eu.kanade.tachiyomi.source.model.SChapter -import uy.kohesive.injekt.injectLazy -import yokai.domain.chapter.interactor.GetChapter -import yokai.domain.manga.interactor.GetManga - -abstract class DelegatedHttpSource { - - var delegate: HttpSource? = null - abstract val domainName: String - - protected val getChapter: GetChapter by injectLazy() - protected val getManga: GetManga by injectLazy() - - protected val network: NetworkHelper by injectLazy() - - abstract fun canOpenUrl(uri: Uri): Boolean - abstract fun chapterUrl(uri: Uri): String? - open fun pageNumber(uri: Uri): Int? = uri.pathSegments.lastOrNull()?.toIntOrNull() - abstract suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? - - protected open suspend fun getMangaInfo(url: String): Manga? { - val id = delegate?.id ?: return null - val manga = Manga.create(url, "", id) - val networkManga = delegate?.getMangaDetails(manga.copy()) ?: return null - val newManga = MangaImpl().apply { - this.url = url - title = try { networkManga.title } catch (e: Exception) { "" } - source = id - } - newManga.copyFrom(networkManga) - return newManga - } - - suspend fun getChapters(url: String): List? { - val id = delegate?.id ?: return null - val manga = Manga.create(url, "", id) - return delegate?.getChapterList(manga) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Cubari.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Cubari.kt index 78816561fe..a0d0db6da4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Cubari.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/Cubari.kt @@ -1,22 +1,31 @@ package eu.kanade.tachiyomi.source.online.all import android.net.Uri -import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.toChapter import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.DelegatedHttpSource +import eu.kanade.tachiyomi.source.online.HttpSource import java.util.Locale import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.withContext import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import yokai.domain.chapter.interactor.GetChapter +import yokai.domain.manga.interactor.GetManga import yokai.i18n.MR import yokai.util.lang.getString -class Cubari : DelegatedHttpSource() { +class Cubari(delegate: HttpSource) : + DelegatedHttpSource(delegate) { + + private val getManga: GetManga = Injekt.get() + private val getChapter: GetChapter = Injekt.get() + + override val lang = "all" + override val domainName: String = "cubari" override fun canOpenUrl(uri: Uri): Boolean = true @@ -24,24 +33,24 @@ class Cubari : DelegatedHttpSource() { override fun pageNumber(uri: Uri): Int? = uri.pathSegments.getOrNull(4)?.toIntOrNull() - override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? { + override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? { val cubariType = uri.pathSegments.getOrNull(1)?.lowercase(Locale.ROOT) ?: return null val cubariPath = uri.pathSegments.getOrNull(2) ?: return null val chapterNumber = uri.pathSegments.getOrNull(3)?.replace("-", ".")?.toFloatOrNull() ?: return null val mangaUrl = "/read/$cubariType/$cubariPath" return withContext(Dispatchers.IO) { val deferredManga = async { - getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!) ?: getMangaInfo(mangaUrl) + getManga.awaitByUrlAndSource(mangaUrl, delegate.id) ?: getMangaDetailsByUrl(mangaUrl) } val deferredChapters = async { - getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!)?.let { manga -> + getManga.awaitByUrlAndSource(mangaUrl, delegate.id)?.let { manga -> val chapters = getChapter.awaitAll(manga, false) val chapter = findChapter(chapters, cubariType, chapterNumber) if (chapter != null) { return@async chapters } } - getChapters(mangaUrl) + getChapterListByUrl(mangaUrl) } val manga = deferredManga.await() val chapters = deferredChapters.await() @@ -50,11 +59,7 @@ class Cubari : DelegatedHttpSource() { ?: error( context.getString(MR.strings.chapter_not_found), ) - if (manga != null) { - Triple(trueChapter, manga, chapters.orEmpty()) - } else { - null - } + Triple(trueChapter, manga, chapters) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt index 15f011c18e..f87714d5d0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/all/MangaDex.kt @@ -1,15 +1,15 @@ package eu.kanade.tachiyomi.source.online.all import android.net.Uri -import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.toChapter import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.DelegatedHttpSource +import eu.kanade.tachiyomi.source.online.HttpSource import java.util.Locale import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -20,10 +20,15 @@ import okhttp3.CacheControl import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import yokai.domain.manga.interactor.GetManga import yokai.i18n.MR import yokai.util.lang.getString -class MangaDex : DelegatedHttpSource() { +class MangaDex(delegate: HttpSource) : DelegatedHttpSource(delegate) { + + private val getManga: GetManga = Injekt.get() + + override val lang: String = "all" override val domainName: String = "mangadex" @@ -42,13 +47,13 @@ class MangaDex : DelegatedHttpSource() { return uri.pathSegments.getOrNull(2)?.toIntOrNull() } - override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? { + override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? { val url = chapterUrl(uri) ?: return null val request = GET("https:///api.mangadex.org/v2$url", delegate!!.headers, CacheControl.FORCE_NETWORK) val response = network.client.newCall(request).await() if (response.code != 200) throw Exception("HTTP error ${response.code}") - val body = response.body.string().orEmpty() + val body = response.body.string() if (body.isEmpty()) { throw Exception("Null Response") } @@ -56,28 +61,19 @@ class MangaDex : DelegatedHttpSource() { val jsonObject = Json.decodeFromString(body) val dataObject = jsonObject.data ?: throw Exception("Chapter not found") val mangaId = dataObject.mangaId ?: throw Exception("No manga associated with chapter") - val langCode = getRealLangCode(dataObject.language ?: "en").uppercase(Locale.getDefault()) - // Use the correct MangaDex source based on the language code, or the api will not return - // the correct chapter list - delegate = sourceManager.getOnlineSources().find { it.toString() == "MangaDex ($langCode)" } - ?: error("Source not found") val mangaUrl = "/manga/$mangaId/" return withContext(Dispatchers.IO) { val deferredManga = async { - getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!) ?: getMangaInfo(mangaUrl) + getManga.awaitByUrlAndSource(mangaUrl, delegate.id) ?: getMangaDetailsByUrl(mangaUrl) } - val deferredChapters = async { getChapters(mangaUrl) } + val deferredChapters = async { getChapterListByUrl(mangaUrl) } val manga = deferredManga.await() val chapters = deferredChapters.await() val context = Injekt.get().context - val trueChapter = chapters?.find { it.url == "/api$url" }?.toChapter() ?: error( + val trueChapter = chapters.find { it.url == "/api$url" }?.toChapter() ?: error( context.getString(MR.strings.chapter_not_found), ) - if (manga != null) { - Triple(trueChapter, manga, chapters.orEmpty()) - } else { - null - } + Triple(trueChapter, manga, chapters) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/FoolSlide.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/FoolSlide.kt deleted file mode 100644 index adeec2e93c..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/FoolSlide.kt +++ /dev/null @@ -1,110 +0,0 @@ -package eu.kanade.tachiyomi.source.online.english - -import android.net.Uri -import eu.kanade.tachiyomi.data.database.models.Chapter -import eu.kanade.tachiyomi.data.database.models.MangaImpl -import eu.kanade.tachiyomi.data.database.models.toChapter -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.domain.manga.models.Manga -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.await -import eu.kanade.tachiyomi.source.model.SChapter -import eu.kanade.tachiyomi.source.online.DelegatedHttpSource -import eu.kanade.tachiyomi.util.asJsoup -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.withContext -import okhttp3.FormBody -import okhttp3.Request -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import yokai.i18n.MR -import yokai.util.lang.getString - -open class FoolSlide(override val domainName: String, private val urlModifier: String = "") : - DelegatedHttpSource - () { - - override fun canOpenUrl(uri: Uri): Boolean = true - - override fun chapterUrl(uri: Uri): String? { - val offset = if (urlModifier.isEmpty()) 0 else 1 - val mangaName = uri.pathSegments.getOrNull(1 + offset) ?: return null - val lang = uri.pathSegments.getOrNull(2 + offset) ?: return null - val volume = uri.pathSegments.getOrNull(3 + offset) ?: return null - val chapterNumber = uri.pathSegments.getOrNull(4 + offset) ?: return null - val subChapterNumber = uri.pathSegments.getOrNull(5 + offset)?.toIntOrNull()?.toString() - return "$urlModifier/read/" + listOfNotNull( - mangaName, - lang, - volume, - chapterNumber, - subChapterNumber, - ).joinToString("/") + "/" - } - - override fun pageNumber(uri: Uri): Int? { - val count = uri.pathSegments.count() - if (count > 2 && uri.pathSegments[count - 2] == "page") { - return super.pageNumber(uri) - } - return null - } - - override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? { - val offset = if (urlModifier.isEmpty()) 0 else 1 - val mangaName = uri.pathSegments.getOrNull(1 + offset) ?: return null - var chapterNumber = uri.pathSegments.getOrNull(4 + offset) ?: return null - val subChapterNumber = uri.pathSegments.getOrNull(5 + offset)?.toIntOrNull() - if (subChapterNumber != null) { - chapterNumber += ".$subChapterNumber" - } - return withContext(Dispatchers.IO) { - val mangaUrl = "$urlModifier/series/$mangaName/" - val sourceId = delegate?.id ?: return@withContext null - val deferredManga = async { - getManga.awaitByUrlAndSource(mangaUrl, sourceId) ?: getManga(mangaUrl) - } - val chapterUrl = chapterUrl(uri) - val deferredChapters = async { getChapters(mangaUrl) } - val manga = deferredManga.await() - val chapters = deferredChapters.await() - val context = Injekt.get().context - val trueChapter = chapters?.find { it.url == chapterUrl }?.toChapter() ?: error( - context.getString(MR.strings.chapter_not_found), - ) - if (manga != null) Triple(trueChapter, manga, chapters) else null - } - } - - open suspend fun getManga(url: String): Manga? { - val request = GET("${delegate!!.baseUrl}$url") - val document = network.client.newCall(allowAdult(request)).await().asJsoup() - val mangaDetailsInfoSelector = "div.info" - val infoElement = document.select(mangaDetailsInfoSelector).first()?.text() ?: return null - return MangaImpl().apply { - this.url = url - source = delegate?.id ?: -1 - title = infoElement.substringAfter("Title:").substringBefore("Author:").trim() - author = infoElement.substringAfter("Author:").substringBefore("Artist:").trim() - artist = infoElement.substringAfter("Artist:").substringBefore("Synopsis:").trim() - description = infoElement.substringAfter("Synopsis:").trim() - thumbnail_url = document.select("div.thumbnail img").firstOrNull()?.attr("abs:src")?.trim() - } - } - - /** - * Transform a GET request into a POST request that automatically authorizes all adult content - */ - private fun allowAdult(request: Request) = allowAdult(request.url.toString()) - - private fun allowAdult(url: String): Request { - return POST( - url, - body = FormBody.Builder() - .add("adult", "true") - .build(), - ) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/KireiCake.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/KireiCake.kt deleted file mode 100644 index ecad868775..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/KireiCake.kt +++ /dev/null @@ -1,28 +0,0 @@ -package eu.kanade.tachiyomi.source.online.english - -import eu.kanade.tachiyomi.data.database.models.MangaImpl -import eu.kanade.tachiyomi.domain.manga.models.Manga -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.await -import eu.kanade.tachiyomi.util.asJsoup -import eu.kanade.tachiyomi.util.lang.capitalizeWords - -class KireiCake : FoolSlide("kireicake") { - - override suspend fun getManga(url: String): Manga? { - val request = GET("${delegate!!.baseUrl}$url") - val document = network.client.newCall(request).await().asJsoup() - val mangaDetailsInfoSelector = "div.info" - return MangaImpl().apply { - this.url = url - source = delegate?.id ?: -1 - title = document.select("$mangaDetailsInfoSelector li:has(b:contains(title))").first() - ?.ownText()?.substringAfter(":")?.trim() - ?: url.split("/").last().replace("_", " " + "").capitalizeWords() - description = - document.select("$mangaDetailsInfoSelector li:has(b:contains(description))").first() - ?.ownText()?.substringAfter(":") - thumbnail_url = document.select("div.thumbnail img").firstOrNull()?.attr("abs:src") - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/MangaPlus.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/MangaPlus.kt index 710b6e4793..ebcbc527ce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/MangaPlus.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/MangaPlus.kt @@ -1,24 +1,31 @@ package eu.kanade.tachiyomi.source.online.english import android.net.Uri -import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.toChapter import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.DelegatedHttpSource +import eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.withContext import okhttp3.CacheControl import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import yokai.domain.manga.interactor.GetManga import yokai.i18n.MR import yokai.util.lang.getString -class MangaPlus : DelegatedHttpSource() { +class MangaPlus(delegate: HttpSource) : + DelegatedHttpSource(delegate) { + + private val getManga: GetManga = Injekt.get() + + override val lang: String get() = delegate.lang + override val domainName: String = "jumpg-webapi.tokyo-cdn" private val titleIdRegex = @@ -34,11 +41,11 @@ class MangaPlus : DelegatedHttpSource() { override fun pageNumber(uri: Uri): Int? = null - override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? { + override suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? { val url = chapterUrl(uri) ?: return null val request = GET( chapterUrlTemplate.replace("##", uri.pathSegments[1]), - delegate!!.headers, + delegate.headers, CacheControl.FORCE_NETWORK, ) return withContext(Dispatchers.IO) { @@ -53,26 +60,22 @@ class MangaPlus : DelegatedHttpSource() { val trimmedTitle = title.substring(0, title.length - 1) val mangaUrl = "#/titles/$titleId" val deferredManga = async { - getManga.awaitByUrlAndSource(mangaUrl, delegate?.id!!) ?: getMangaInfo(mangaUrl) + getManga.awaitByUrlAndSource(mangaUrl, delegate.id) ?: getMangaDetailsByUrl(mangaUrl) } - val deferredChapters = async { getChapters(mangaUrl) } + val deferredChapters = async { getChapterListByUrl(mangaUrl) } val manga = deferredManga.await() val chapters = deferredChapters.await() val context = Injekt.get().context - val trueChapter = chapters?.find { it.url == url }?.toChapter() ?: error( + val trueChapter = chapters.find { it.url == url }?.toChapter() ?: error( context.getString(MR.strings.chapter_not_found), ) - if (manga != null) { - Triple( - trueChapter, - manga.apply { - this.title = trimmedTitle - }, - chapters, - ) - } else { - null - } + Triple( + trueChapter, + manga.apply { + this.title = trimmedTitle + }, + chapters, + ) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index cf0a856541..e2ec854e73 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -12,6 +12,7 @@ import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.History +import eu.kanade.tachiyomi.data.database.models.create import eu.kanade.tachiyomi.data.database.models.defaultReaderType import eu.kanade.tachiyomi.data.database.models.orientationType import eu.kanade.tachiyomi.data.database.models.readingModeType @@ -307,6 +308,7 @@ class ReaderViewModel( return delegatedSource.pageNumber(url)?.minus(1) } + // FIXME: Unused at the moment, handles J2K's delegated deep link, refactor or remove later suspend fun loadChapterURL(url: Uri) { val host = url.host ?: return val context = Injekt.get() @@ -314,9 +316,7 @@ class ReaderViewModel( context.getString(MR.strings.source_not_installed), ) val chapterUrl = delegatedSource.chapterUrl(url) - val sourceId = delegatedSource.delegate?.id ?: error( - context.getString(MR.strings.source_not_installed), - ) + val sourceId = delegatedSource.delegate.id if (chapterUrl != null) { val dbChapter = getChapter.awaitAllByUrl(chapterUrl, false).find { val source = getManga.awaitById(it.manga_id!!)?.source ?: return@find false @@ -334,7 +334,9 @@ class ReaderViewModel( } val info = delegatedSource.fetchMangaFromChapterUrl(url) if (info != null) { - val (chapter, manga, chapters) = info + val (sChapter, sManga, chapters) = info + val manga = Manga.create(sourceId).apply { copyFrom(sManga) } + val chapter = Chapter.create().apply { copyFrom(sChapter) } val id = insertManga.await(manga) manga.id = id ?: manga.id chapter.manga_id = manga.id diff --git a/source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/DelegatedHttpSource.kt b/source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/DelegatedHttpSource.kt new file mode 100644 index 0000000000..cbcf769461 --- /dev/null +++ b/source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/DelegatedHttpSource.kt @@ -0,0 +1,378 @@ +package eu.kanade.tachiyomi.source.online + +import android.net.Uri +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.source.model.SChapter +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import rx.Observable + +abstract class DelegatedHttpSource(val delegate: HttpSource): HttpSource() { + /** + * Returns the request for the popular manga given the page. + * + * @param page the page number to retrieve. + */ + override fun popularMangaRequest(page: Int) = + throw UnsupportedOperationException("Should never be called!") + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun popularMangaParse(response: Response) = + throw UnsupportedOperationException("Should never be called!") + + /** + * Returns the request for the search manga given the page. + * + * @param page the page number to retrieve. + * @param query the search query. + * @param filters the list of filters to apply. + */ + override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = + throw UnsupportedOperationException("Should never be called!") + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun searchMangaParse(response: Response) = + throw UnsupportedOperationException("Should never be called!") + + /** + * Returns the request for latest manga given the page. + * + * @param page the page number to retrieve. + */ + override fun latestUpdatesRequest(page: Int) = + throw UnsupportedOperationException("Should never be called!") + + /** + * Parses the response from the site and returns a [MangasPage] object. + * + * @param response the response from the site. + */ + override fun latestUpdatesParse(response: Response) = + throw UnsupportedOperationException("Should never be called!") + + /** + * Parses the response from the site and returns the details of a manga. + * + * @param response the response from the site. + */ + override fun mangaDetailsParse(response: Response) = + throw UnsupportedOperationException("Should never be called!") + + /** + * Parses the response from the site and returns a list of chapters. + * + * @param response the response from the site. + */ + override fun chapterListParse(response: Response) = + throw UnsupportedOperationException("Should never be called!") + + /** + * Parses the response from the site and returns a SChapter Object. + * + * @param response the response from the site. + */ + override fun chapterPageParse(response: Response) = + throw UnsupportedOperationException("Should never be called!") + + /** + * Parses the response from the site and returns a list of pages. + * + * @param response the response from the site. + */ + override fun pageListParse(response: Response) = + throw UnsupportedOperationException("Should never be called!") + + /** + * Parses the response from the site and returns the absolute url to the source image. + * + * @param response the response from the site. + */ + override fun imageUrlParse(response: Response) = + throw UnsupportedOperationException("Should never be called!") + + abstract val domainName: String + + /** + * Base url of the website without the trailing slash, like: http://mysite.com + */ + override val baseUrl get() = delegate.baseUrl + + /** + * Headers used for requests. + */ + override val headers get() = delegate.headers + + /** + * Whether the source has support for latest updates. + */ + override val supportsLatest get() = delegate.supportsLatest + + /** + * Name of the source. + */ + final override val name get() = delegate.name + + // ===> OPTIONAL FIELDS + + /** + * Id of the source. By default it uses a generated id using the first 16 characters (64 bits) + * of the MD5 of the string: sourcename/language/versionId + * Note the generated id sets the sign bit to 0. + */ + override val id get() = delegate.id + + /** + * Default network client for doing requests. + */ + final override val client get() = delegate.client + + /** + * You must NEVER call super.client if you override this! + */ + open val baseHttpClient: OkHttpClient? = null + open val networkHttpClient: OkHttpClient get() = network.client + + /** + * Visible name of the source. + */ + override fun toString() = delegate.toString() + + /** + * Returns an observable containing a page with a list of manga. Normally it's not needed to + * override this method. + * + * @param page the page number to retrieve. + */ + @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPopularManga")) + override fun fetchPopularManga(page: Int): Observable { + ensureDelegateCompatible() + return delegate.fetchPopularManga(page) + } + + override suspend fun getPopularManga(page: Int): MangasPage { + ensureDelegateCompatible() + return delegate.getPopularManga(page) + } + + /** + * Returns an observable containing a page with a list of manga. Normally it's not needed to + * override this method. + * + * @param page the page number to retrieve. + * @param query the search query. + * @param filters the list of filters to apply. + */ + @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga")) + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + ensureDelegateCompatible() + return delegate.fetchSearchManga(page, query, filters) + } + + override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage { + ensureDelegateCompatible() + return delegate.getSearchManga(page, query, filters) + } + + /** + * Returns an observable containing a page with a list of latest manga updates. + * + * @param page the page number to retrieve. + */ + @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getLatestUpdates")) + override fun fetchLatestUpdates(page: Int): Observable { + ensureDelegateCompatible() + return delegate.fetchLatestUpdates(page) + } + + override suspend fun getLatestUpdates(page: Int): MangasPage { + ensureDelegateCompatible() + return delegate.getLatestUpdates(page) + } + + /** + * Returns an observable with the updated details for a manga. Normally it's not needed to + * override this method. + * + * @param manga the manga to be updated. + */ + @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getMangaDetails")) + override fun fetchMangaDetails(manga: SManga): Observable { + ensureDelegateCompatible() + return delegate.fetchMangaDetails(manga) + } + + /** + * [1.x API] Get the updated details for a manga. + */ + override suspend fun getMangaDetails(manga: SManga): SManga { + ensureDelegateCompatible() + return delegate.getMangaDetails(manga) + } + + /** + * Returns the request for the details of a manga. Override only if it's needed to change the + * url, send different headers or request method like POST. + * + * @param manga the manga to be updated. + */ + override fun mangaDetailsRequest(manga: SManga): Request { + ensureDelegateCompatible() + return delegate.mangaDetailsRequest(manga) + } + + /** + * Returns an observable with the updated chapter list for a manga. Normally it's not needed to + * override this method. If a manga is licensed an empty chapter list observable is returned + * + * @param manga the manga to look for chapters. + */ + @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getChapterList")) + override fun fetchChapterList(manga: SManga): Observable> { + ensureDelegateCompatible() + return delegate.fetchChapterList(manga) + } + + /** + * [1.x API] Get all the available chapters for a manga. + */ + override suspend fun getChapterList(manga: SManga): List { + ensureDelegateCompatible() + return delegate.getChapterList(manga) + } + + /** + * Returns an observable with the page list for a chapter. + * + * @param chapter the chapter whose page list has to be fetched. + */ + @Deprecated("Use the 1.x API instead", replaceWith = ReplaceWith("getPageList")) + override fun fetchPageList(chapter: SChapter): Observable> { + ensureDelegateCompatible() + return delegate.fetchPageList(chapter) + } + + /** + * [1.x API] Get the list of pages a chapter has. + */ + override suspend fun getPageList(chapter: SChapter): List { + ensureDelegateCompatible() + return delegate.getPageList(chapter) + } + + /** + * Returns an observable with the page containing the source url of the image. If there's any + * error, it will return null instead of throwing an exception. + * + * @param page the page whose source image has to be fetched. + */ + @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl")) + override fun fetchImageUrl(page: Page): Observable { + ensureDelegateCompatible() + return delegate.fetchImageUrl(page) + } + + override suspend fun getImageUrl(page: Page): String { + ensureDelegateCompatible() + return delegate.getImageUrl(page) + } + + /** + * Returns the response of the source image. + * + * @param page the page whose source image has to be downloaded. + */ + override suspend fun getImage(page: Page): Response { + ensureDelegateCompatible() + return delegate.getImage(page) + } + + /** + * Returns the url of the provided manga + * + * @since extensions-lib 1.4 + * @param manga the manga + * @return url of the manga + */ + override fun getMangaUrl(manga: SManga): String { + ensureDelegateCompatible() + return delegate.getMangaUrl(manga) + } + + /** + * Returns the url of the provided chapter + * + * @since extensions-lib 1.4 + * @param chapter the chapter + * @return url of the chapter + */ + override fun getChapterUrl(chapter: SChapter): String { + ensureDelegateCompatible() + return delegate.getChapterUrl(chapter) + } + + /** + * Called before inserting a new chapter into database. Use it if you need to override chapter + * fields, like the title or the chapter number. Do not change anything to [manga]. + * + * @param chapter the chapter to be added. + * @param manga the manga of the chapter. + */ + override fun prepareNewChapter(chapter: SChapter, manga: SManga) { + ensureDelegateCompatible() + return delegate.prepareNewChapter(chapter, manga) + } + + /** + * Returns the list of filters for the source. + */ + override fun getFilterList() = delegate.getFilterList() + + abstract fun canOpenUrl(uri: Uri): Boolean + abstract fun chapterUrl(uri: Uri): String? + open fun pageNumber(uri: Uri): Int? = uri.pathSegments.lastOrNull()?.toIntOrNull() + abstract suspend fun fetchMangaFromChapterUrl(uri: Uri): Triple>? + + open suspend fun getMangaDetailsByUrl(url: String): SManga { + val manga = SManga.create().apply { + this.url = url + this.title = "" + } + return delegate.getMangaDetails(manga.copy()) + } + + open suspend fun getChapterListByUrl(url: String): List { + val manga = SManga.create().apply { + this.url = url + this.title = "" + } + return delegate.getChapterList(manga) + } + + protected open fun ensureDelegateCompatible() { + if (versionId != delegate.versionId || lang != delegate.lang) { + throw IncompatibleDelegateException( + "Delegate source is not compatible (" + + "versionId: $versionId <=> ${delegate.versionId}, lang: $lang <=> ${delegate.lang}" + + ")!", + ) + } + } + + class IncompatibleDelegateException(message: String) : RuntimeException(message) + + init { + delegate.bindDelegate(this) + } +} diff --git a/source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt b/source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt index 05494e6062..4faaffe463 100644 --- a/source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -12,21 +12,22 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.util.awaitSingle +import java.net.URI +import java.net.URISyntaxException +import java.security.MessageDigest import okhttp3.Headers import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import rx.Observable import uy.kohesive.injekt.injectLazy -import java.net.URI -import java.net.URISyntaxException -import java.security.MessageDigest /** * A simple implementation for sources from a website. */ @Suppress("unused") abstract class HttpSource : CatalogueSource { + private var delegate: DelegatedHttpSource? = null /** * Network service. @@ -59,7 +60,7 @@ abstract class HttpSource : CatalogueSource { /** * Headers used for requests. */ - val headers: Headers by lazy { headersBuilder().build() } + open val headers: Headers by lazy { headersBuilder().build() } /** * Default network client for doing requests. @@ -67,6 +68,10 @@ abstract class HttpSource : CatalogueSource { open val client: OkHttpClient get() = network.client + fun bindDelegate(delegate: DelegatedHttpSource) { + this.delegate = delegate + } + /** * Generates a unique ID for the source based on the provided [name], [lang] and * [versionId]. It will use the first 16 characters (64 bits) of the MD5 of the string @@ -283,6 +288,13 @@ abstract class HttpSource : CatalogueSource { */ protected abstract fun chapterListParse(response: Response): List + /** + * Parses the response from the site and returns a SChapter Object. + * + * @param response the response from the site. + */ + protected abstract fun chapterPageParse(response: Response): SChapter + /** * Get the list of pages a chapter has. Pages should be returned * in the expected order; the index is ignored. From 42dd857d94051b5b481d87f8eedd2447bcc579ac Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 1 Jan 2025 10:33:37 +0700 Subject: [PATCH 063/181] docs: Sync changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6561794ec8..180950ab91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - [Experimental] Add modified version of LargeTopAppBar that mimic J2K's ExpandedAppBarLayout - Refactor About page to use Compose - Adjust Compose-based pages' transition to match J2K's Conductor transition +- Resolve deprecation warnings + - Kotlin's context-receiver, schedule for removal on Kotlin v2.1.x and planned to be replaced by context-parameters on Kotlin v2.2 + - Project.exec -> Providers.exec + - Remove internal API usage to retrieve Kotlin version for kotlin-stdlib +- Move :core module to :core:main + - Move archive related code to :core:archive (@AntsyLich) ## [1.9.7] From fa84ce8fe87af6f4018973fa3b32155811cd3890 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 1 Jan 2025 14:02:57 +0700 Subject: [PATCH 064/181] refactor: Reduce dependant towards RecentsPresenter --- .../appwidget/UpdatesGridGlanceWidget.kt | 33 ++++++++++- .../tachiyomi/ui/recents/RecentsPresenter.kt | 59 ++++++++++++------- .../domain/recents/interactor/GetRecents.kt | 9 +++ 3 files changed, 79 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt index 0cd9f75e50..421b83e367 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt @@ -40,9 +40,13 @@ import eu.kanade.tachiyomi.util.system.launchIO import java.util.Calendar import java.util.Date import kotlin.math.min +import kotlin.math.roundToLong import kotlinx.coroutines.MainScope +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import yokai.domain.manga.models.cover +import yokai.domain.recents.interactor.GetRecents class UpdatesGridGlanceWidget : GlanceAppWidget() { private val app: Application by injectLazy() @@ -64,6 +68,33 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() { } } + // FIXME: Don't depends on RecentsPresenter + private suspend fun getUpdates(customAmount: Int = 0, getRecents: GetRecents = Injekt.get()): List> { + return getRecents + .awaitUpdates( + limit = when { + customAmount > 0 -> (customAmount * 1.5).roundToLong() + else -> 25L + } + ) + .mapNotNull { + when { + it.chapter.read || it.chapter.id == null -> RecentsPresenter.getNextChapter(it.manga) + it.history.id == null -> RecentsPresenter.getFirstUpdatedChapter(it.manga, it.chapter) + else -> it.chapter + } ?: return@mapNotNull null + it + } + .asSequence() + .distinctBy { it.manga.id } + .sortedByDescending { it.history.last_read } + // nChapterItems + nAdditionalItems + cReadingItems + .take((RecentsPresenter.UPDATES_CHAPTER_LIMIT * 2) + RecentsPresenter.UPDATES_READING_LIMIT_LOWER) + .filter { it.manga.id != null } + .map { it.manga to it.history.last_read } + .toList() + } + fun loadData(list: List>? = null) { coroutineScope.launchIO { // Don't show anything when lock is active @@ -80,7 +111,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() { .flatMap { manager.getAppWidgetSizes(it) } .maxBy { it.height.value * it.width.value } .calculateRowAndColumnCount() - val processList = list ?: RecentsPresenter.getRecentManga(customAmount = min(50, rowCount * columnCount)) + val processList = list ?: getUpdates(customAmount = min(50, rowCount * columnCount)) data = prepareList(processList, rowCount * columnCount) ids.forEach { update(app, it) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt index 322420ecc8..b29671a580 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt @@ -379,20 +379,22 @@ class RecentsPresenter( f2.second.date_fetch.compareTo(f1.second.date_fetch) } } - .take(4).map { - RecentMangaItem(it.first, it.second, newChaptersHeader) - }.toMutableList() + .take(UPDATES_CHAPTER_LIMIT) + .map { RecentMangaItem(it.first, it.second, newChaptersHeader) } + .toMutableList() val cReadingItems = - pairs.filter { it.first.history.id != null }.take(9 - nChaptersItems.size).map { - RecentMangaItem(it.first, it.second, continueReadingHeader) - }.toMutableList() + pairs.filter { it.first.history.id != null } + .take(UPDATES_READING_LIMIT_UPPER - nChaptersItems.size) + .map { RecentMangaItem(it.first, it.second, continueReadingHeader) } + .toMutableList() if (nChaptersItems.isNotEmpty()) { nChaptersItems.add(RecentMangaItem(header = newChaptersHeader)) } if (cReadingItems.isNotEmpty()) { cReadingItems.add(RecentMangaItem(header = continueReadingHeader)) } - val nAdditionsItems = pairs.filter { it.first.chapter.id == null }.take(4) + val nAdditionsItems = pairs.filter { it.first.chapter.id == null } + .take(UPDATES_CHAPTER_LIMIT) .map { RecentMangaItem(it.first, it.second, newAdditionsHeader) } listOf(nChaptersItems, cReadingItems, nAdditionsItems).sortedByDescending { it.firstOrNull()?.mch?.history?.last_read ?: 0L @@ -483,20 +485,6 @@ class RecentsPresenter( return Triple(sortedChapters, firstChapter, extraCount) } - private suspend fun getNextChapter(manga: Manga): Chapter? { - val mangaId = manga.id ?: return null - val chapters = getChapter.awaitUnread(mangaId, true) - return ChapterSort(manga, chapterFilter, preferences).getNextChapter(chapters, false) - } - - private suspend fun getFirstUpdatedChapter(manga: Manga, chapter: Chapter): Chapter? { - val mangaId = manga.id ?: return null - val chapters = getChapter.awaitUnread(mangaId, true) - return chapters.sortedWith(ChapterSort(manga, chapterFilter, preferences).sortComparator(true)).find { - abs(it.date_fetch - chapter.date_fetch) <= TimeUnit.HOURS.toMillis(12) - } - } - override fun onDestroy() { super.onDestroy() lastRecents = recentItems @@ -727,6 +715,31 @@ class RecentsPresenter( var SHORT_LIMIT = 25 private set + suspend fun getNextChapter( + manga: Manga, + getChapter: GetChapter = Injekt.get(), + chapterFilter: ChapterFilter = Injekt.get(), + preferences: PreferencesHelper = Injekt.get(), + ): Chapter? { + val mangaId = manga.id ?: return null + val chapters = getChapter.awaitUnread(mangaId, true) + return ChapterSort(manga, chapterFilter, preferences).getNextChapter(chapters, false) + } + + suspend fun getFirstUpdatedChapter( + manga: Manga, + chapter: Chapter, + getChapter: GetChapter = Injekt.get(), + chapterFilter: ChapterFilter = Injekt.get(), + preferences: PreferencesHelper = Injekt.get(), + ): Chapter? { + val mangaId = manga.id ?: return null + val chapters = getChapter.awaitUnread(mangaId, true) + return chapters.sortedWith(ChapterSort(manga, chapterFilter, preferences).sortComparator(true)).find { + abs(it.date_fetch - chapter.date_fetch) <= TimeUnit.HOURS.toMillis(12) + } + } + suspend fun getRecentManga(includeRead: Boolean = false, customAmount: Int = 0): List> { val presenter = RecentsPresenter() presenter.viewType = RecentsViewType.UngroupedAll @@ -741,5 +754,9 @@ class RecentsPresenter( .filter { it.mch.manga.id != null } .map { it.mch.manga to it.mch.history.last_read } } + + const val UPDATES_CHAPTER_LIMIT = 4 + const val UPDATES_READING_LIMIT_UPPER = 9 + const val UPDATES_READING_LIMIT_LOWER = 5 } } diff --git a/app/src/main/java/yokai/domain/recents/interactor/GetRecents.kt b/app/src/main/java/yokai/domain/recents/interactor/GetRecents.kt index 9cf8c41fe6..2ca0923d17 100644 --- a/app/src/main/java/yokai/domain/recents/interactor/GetRecents.kt +++ b/app/src/main/java/yokai/domain/recents/interactor/GetRecents.kt @@ -55,4 +55,13 @@ class GetRecents( return historyRepository.getRecentsAll(includeRead, filterScanlators, search, limit, actualOffset) } + + suspend fun awaitUpdates(limit: Long = 0L): List = + historyRepository.getRecentsAll( + includeRead = false, + filterScanlators = true, + search = "", + limit = limit, + offset = 0L + ) } From 02c2b370b446f4e1ac3adaae695ba8547bd36a07 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 1 Jan 2025 01:11:24 +0100 Subject: [PATCH 065/181] i18n: Translations update from Hosted Weblate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ahmad Ansori Palembani Co-authored-by: Hosted Weblate Co-authored-by: Infy's Tagalog Translations Co-authored-by: zhongfly Co-authored-by: ɴᴇᴋᴏ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai-plurals/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai-plurals/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/fil/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/id/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/zh_Hant/ Translation: Yōkai/Yōkai Translation: Yōkai/Yōkai Plurals --- .../commonMain/moko-resources/ar/plurals.xml | 33 +--- .../commonMain/moko-resources/ar/strings.xml | 8 +- .../commonMain/moko-resources/az/plurals.xml | 8 +- .../commonMain/moko-resources/az/strings.xml | 8 +- .../commonMain/moko-resources/bg/plurals.xml | 25 +-- .../commonMain/moko-resources/bg/strings.xml | 8 +- .../commonMain/moko-resources/bn/plurals.xml | 25 +-- .../commonMain/moko-resources/bn/strings.xml | 8 +- .../commonMain/moko-resources/ca/plurals.xml | 18 +-- .../commonMain/moko-resources/ca/strings.xml | 8 +- .../commonMain/moko-resources/ceb/strings.xml | 7 +- .../commonMain/moko-resources/cs/plurals.xml | 30 +--- .../commonMain/moko-resources/cs/strings.xml | 8 +- .../commonMain/moko-resources/cv/plurals.xml | 14 +- .../commonMain/moko-resources/cv/strings.xml | 3 +- .../commonMain/moko-resources/de/plurals.xml | 29 +--- .../commonMain/moko-resources/de/strings.xml | 8 +- .../commonMain/moko-resources/el/plurals.xml | 29 +--- .../commonMain/moko-resources/el/strings.xml | 8 +- .../commonMain/moko-resources/eo/plurals.xml | 13 +- .../commonMain/moko-resources/eo/strings.xml | 3 +- .../commonMain/moko-resources/es/plurals.xml | 30 +--- .../commonMain/moko-resources/es/strings.xml | 8 +- .../commonMain/moko-resources/eu/plurals.xml | 24 +-- .../commonMain/moko-resources/eu/strings.xml | 7 +- .../commonMain/moko-resources/fa/strings.xml | 8 +- .../commonMain/moko-resources/fi/plurals.xml | 24 +-- .../commonMain/moko-resources/fi/strings.xml | 4 +- .../commonMain/moko-resources/fil/plurals.xml | 30 +--- .../commonMain/moko-resources/fil/strings.xml | 104 +++++++++++- .../commonMain/moko-resources/fr/plurals.xml | 31 +--- .../commonMain/moko-resources/fr/strings.xml | 8 +- .../commonMain/moko-resources/gl/strings.xml | 8 +- .../commonMain/moko-resources/hi/plurals.xml | 16 +- .../commonMain/moko-resources/hi/strings.xml | 8 +- .../commonMain/moko-resources/hr/plurals.xml | 31 +--- .../commonMain/moko-resources/hr/strings.xml | 8 +- .../commonMain/moko-resources/hu/plurals.xml | 17 +- .../commonMain/moko-resources/hu/strings.xml | 6 +- .../commonMain/moko-resources/in/plurals.xml | 29 +--- .../commonMain/moko-resources/in/strings.xml | 11 +- .../commonMain/moko-resources/it/plurals.xml | 31 +--- .../commonMain/moko-resources/it/strings.xml | 8 +- .../commonMain/moko-resources/iw/plurals.xml | 18 +-- .../commonMain/moko-resources/iw/strings.xml | 8 +- .../commonMain/moko-resources/ja/plurals.xml | 29 +--- .../commonMain/moko-resources/ja/strings.xml | 8 +- .../commonMain/moko-resources/jv/strings.xml | 7 +- .../commonMain/moko-resources/ka/strings.xml | 3 +- .../commonMain/moko-resources/kk/plurals.xml | 15 +- .../commonMain/moko-resources/kk/strings.xml | 8 +- .../commonMain/moko-resources/km/plurals.xml | 10 +- .../commonMain/moko-resources/km/strings.xml | 8 +- .../commonMain/moko-resources/ko/plurals.xml | 29 +--- .../commonMain/moko-resources/ko/strings.xml | 9 +- .../commonMain/moko-resources/lt/strings.xml | 3 +- .../commonMain/moko-resources/lv/plurals.xml | 17 +- .../commonMain/moko-resources/lv/strings.xml | 5 +- .../commonMain/moko-resources/mn/strings.xml | 6 - .../commonMain/moko-resources/ms/plurals.xml | 29 +--- .../commonMain/moko-resources/ms/strings.xml | 8 +- .../commonMain/moko-resources/my/plurals.xml | 9 +- .../commonMain/moko-resources/my/strings.xml | 8 +- .../moko-resources/nb-rNO/plurals.xml | 30 +--- .../moko-resources/nb-rNO/strings.xml | 8 +- .../commonMain/moko-resources/ne/plurals.xml | 30 +--- .../commonMain/moko-resources/ne/strings.xml | 8 +- .../commonMain/moko-resources/nl/plurals.xml | 30 +--- .../commonMain/moko-resources/nl/strings.xml | 8 +- .../commonMain/moko-resources/om/plurals.xml | 10 +- .../commonMain/moko-resources/om/strings.xml | 7 +- .../commonMain/moko-resources/pl/plurals.xml | 32 +--- .../commonMain/moko-resources/pl/strings.xml | 8 +- .../moko-resources/pt-rBR/plurals.xml | 31 +--- .../moko-resources/pt-rBR/strings.xml | 17 +- .../commonMain/moko-resources/pt/plurals.xml | 31 +--- .../commonMain/moko-resources/pt/strings.xml | 8 +- .../commonMain/moko-resources/ro/plurals.xml | 31 +--- .../commonMain/moko-resources/ro/strings.xml | 8 +- .../commonMain/moko-resources/ru/plurals.xml | 32 +--- .../commonMain/moko-resources/ru/strings.xml | 8 +- .../commonMain/moko-resources/sc/plurals.xml | 30 +--- .../commonMain/moko-resources/sc/strings.xml | 8 +- .../commonMain/moko-resources/si/strings.xml | 5 - .../commonMain/moko-resources/sk/plurals.xml | 17 +- .../commonMain/moko-resources/sk/strings.xml | 8 +- .../commonMain/moko-resources/sq/strings.xml | 3 +- .../commonMain/moko-resources/sr/plurals.xml | 31 +--- .../commonMain/moko-resources/sr/strings.xml | 8 +- .../commonMain/moko-resources/sv/plurals.xml | 30 +--- .../commonMain/moko-resources/sv/strings.xml | 8 +- .../commonMain/moko-resources/th/plurals.xml | 29 +--- .../commonMain/moko-resources/th/strings.xml | 8 +- .../commonMain/moko-resources/tl/strings.xml | 3 +- .../commonMain/moko-resources/tr/plurals.xml | 30 +--- .../commonMain/moko-resources/tr/strings.xml | 8 +- .../commonMain/moko-resources/uk/plurals.xml | 32 +--- .../commonMain/moko-resources/uk/strings.xml | 8 +- .../commonMain/moko-resources/vi/plurals.xml | 29 +--- .../commonMain/moko-resources/vi/strings.xml | 8 +- .../moko-resources/zh-rCN/plurals.xml | 33 +--- .../moko-resources/zh-rCN/strings.xml | 153 ++++++++++++++---- .../moko-resources/zh-rTW/plurals.xml | 29 +--- .../moko-resources/zh-rTW/strings.xml | 26 ++- 104 files changed, 340 insertions(+), 1530 deletions(-) diff --git a/i18n/src/commonMain/moko-resources/ar/plurals.xml b/i18n/src/commonMain/moko-resources/ar/plurals.xml index 2bdf73bcba..ee1cc5e004 100644 --- a/i18n/src/commonMain/moko-resources/ar/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ar/plurals.xml @@ -8,7 +8,6 @@ يتوفَّر %d تحديثًا للإضافات يتوفَّر %d تحديث للإضافات - لا توجد فئات فئة @@ -17,16 +16,6 @@ %d فئةً %d فئة - - - انتهت الصفحات - تبقَّت صفحة - تبقَّت صفحتان - تبقَّت %1$d صفحات - تبقَّت %1$d صفحةً - تبقَّت %1$d صفحة - - لم يُزل أيُّ فصل من المصدر: \n%2$s @@ -47,7 +36,6 @@ \n%2$s \nأأحذف تنزيلاتهم؟ - ألا أزيل أيَّ فصل؟ أأزيل فصلًا منزَّلًا؟ @@ -56,7 +44,6 @@ أأزيل %1$d فصلًا منزَّلًا؟ أأزيل %1$d فصل منزَّل؟ - بعد %1$s دقيقة بعد %1$s دقيقة @@ -65,7 +52,6 @@ بعد %1$s دقائق بعد %1$s دقائق - تم التنظيف. تمت إزالة %d مجلد تم التنظيف. تمت إزالة %d مجلد @@ -74,7 +60,6 @@ تم التنظيف. تمت إزالة %d مجلدات تم التنظيف. تمت إزالة %d مجلدات - لصفر عنوان لعنوان @@ -83,7 +68,6 @@ ل‍ %d عنوانًا ل‍ %d عنوان - ولا أيِّ فصل وفصل @@ -92,7 +76,6 @@ و %1$d فصلًا و %1$d فصل - تمَّ في %1$s وبدون أخطاء تمَّ في %1$s وفيه خطأ @@ -101,7 +84,6 @@ تمَّ في %1$s وفيه %2$s خطأً تمَّ في %1$s وفيه %2$s خطأ - لا يوجد أيُّ فصل فصل واحد @@ -110,7 +92,6 @@ %1$s فصلًا %1$s فصل - لم تُرحَّل أيُّ سلسلة رُحِّلت السلسلة @@ -119,7 +100,6 @@ رُحِّلت %d سلسلةً رُحِّلت %d سلسلةٍ - ألا أنسخ أيَّ سلسلة؟ أأنسخ السلسلة؟ @@ -128,7 +108,6 @@ أأنسخ %1$d (بتخطي %2$s) سلسلةً؟ أأنسخ %1$d (بتخطي %2$s) سلسلةٍ؟ - ألا أرحِّل أيَّ سلسلة؟ أأرحِّل السلسلة؟ @@ -137,7 +116,6 @@ أأرحِّل %1$d (بتخطِّي %2$s) سلسلةً؟ أأرحِّل %1$d (بتخطِّي %2$s) سلسلةٍ؟ - لا توجد صفحات صفحة @@ -146,7 +124,6 @@ %1$d صفحةً %1$d صفحة - لا يوجد تحديث معلَّق يوجد تحديث معلَّق @@ -155,7 +132,6 @@ يوجد %d تحديثًا معلَّقًا يوجد %d تحديث معلَّق - لم تُحدَّث أيُّ إضافة حُدِّثت إضافة @@ -164,7 +140,6 @@ حُدِّثت %d إضافةً حُدِّثت %d إضافة - لا يُتخطَّى أيُّ فصل يُتخطَّى فصل، وذلك إما لأن المصدر مفقود أو لأنه مصفًّى @@ -173,7 +148,6 @@ يُتخطَّى %d فصلًا، وذلك إما لأن المصدر مفقود أو لأنهم مصفَّون يُتخطَّى %d فصل، وذلك إما لأن المصدر مفقود أو لأنهم مصفَّون - تم تنظيف الملفات المؤقتة. تم حذف %d من الملفات تم تنظيف الملفات المؤقتة. تم حذف %d من الملفات @@ -182,7 +156,6 @@ تم تنظيف الملفات المؤقتة. تم حذف %d من الملفات تم تنظيف الملفات المؤقتة. تم حذف %d من الملفات - لا يوجد فصل تالٍ لم يُقرأ الفصل غير المقروء التالي @@ -191,7 +164,6 @@ %d فصلًا تاليًا لم يُقرؤوا %d فصل تالٍ لم يُقرؤوا - %d انواع المسلسلات %d انواع المسلسلات @@ -200,7 +172,6 @@ %d عدة انواع من المسلسلات %d انواع من المسلسلات - %d مصدر %d مصدر @@ -209,7 +180,6 @@ %d مصادر %d مصادر - %d لغة %d لغة @@ -218,7 +188,6 @@ %d لغات %d لغات - %d وضع %d وضع @@ -227,4 +196,4 @@ %d اوضاع %d اوضاع - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ar/strings.xml b/i18n/src/commonMain/moko-resources/ar/strings.xml index dae24365fe..8fbfe8ca83 100644 --- a/i18n/src/commonMain/moko-resources/ar/strings.xml +++ b/i18n/src/commonMain/moko-resources/ar/strings.xml @@ -753,11 +753,6 @@ حسب أرقام الفصول حسب ترتيب المصدر لم يعلَّم - يتطلب TachiyomiJ2K الوصول إلى جميع الملفات لينزِّل الفصول. اضغط هنا ثم مكِّن «السماح بالوصول لإدارة جميع الملفات.» - يحتاج TachiyomiJ2K للوصول إلى كلِّ الملفَّات في أندرويد ١١، فبذلك ينزِّل الفصول ويحتاط ويقرأ السلاسل المنزَّلة. - \n - \nمكِّن «اسمح بإدارة كافة الملفات.» عندما تراها في الشاشة التالية - إذن الوصول إلى الملفات مطلوب تحذير بحث %1$s الاتجاه @@ -904,7 +899,6 @@ بنفسجي ٥٪ سلسلة وكيل المستخدم المبدئية - تنسيق RARv5 ليس مدعومًا تتطلَّب بعض اللغات إعادة التشغيل لتظهر صحيحةً الفصول المنزَّلة التحميل تلقائيا أثناء القراءة @@ -963,4 +957,4 @@ معلومات التنقيح الأنشطة التي تعمل في الخلفية شارك الغلاف - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/az/plurals.xml b/i18n/src/commonMain/moko-resources/az/plurals.xml index 31d3381bad..622df450e9 100644 --- a/i18n/src/commonMain/moko-resources/az/plurals.xml +++ b/i18n/src/commonMain/moko-resources/az/plurals.xml @@ -4,14 +4,8 @@ Endirilmiş %1$d bölüm silinsin\? Endirilmiş %1$d bölüm silinsin\? - %1$s bölüm %1$s bölüm - - - %1$d səhifə qaldı - %1$d səhifə qaldı - - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/az/strings.xml b/i18n/src/commonMain/moko-resources/az/strings.xml index 1558940540..01f3695acd 100644 --- a/i18n/src/commonMain/moko-resources/az/strings.xml +++ b/i18n/src/commonMain/moko-resources/az/strings.xml @@ -2,7 +2,6 @@ Ad Daha çox - TachiyomiJ2K bölümləri yükləmək üçün bütün fayllara giriş tələb edir. Bura klikləyin, sonra \"Bütün faylları idarə etmək üçün girişə icazə verin\" funksiyasını aktivləşdirin. Manga Komiks Davam edir @@ -40,17 +39,12 @@ Sırala Bölümlər tapılmadı Səhifələr tapılmadı - Fayl icazələri tələb olunur - TachiyomiJ2K bölümləri yükləmək, avtomatik ehtiyat nüsxələri yaratmaq və lokal manqa oxumaq üçün Android 11-də bütün fayllara giriş icazəsi olmalıdır. - \n - \nNövbəti ekranda \"Bütün faylları idarə etmək üçün girişə icazə verin\" seçimini aktivləşdirin. Manhwa Manhua Boş olduqda kilidlə Bölümlər Oxundu olaraq işarələndi Bölümlər silindi. - RARv5 formatı dəstəklənmir Bütün endirilənlər silinsin\? Silmək üçün bölüm yoxdur Mənbə sırasıyla @@ -59,4 +53,4 @@ Skanlyatorlar Kateqoriya Kateqoriyalar - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/bg/plurals.xml b/i18n/src/commonMain/moko-resources/bg/plurals.xml index 92490a53d9..c8dc3ddf74 100644 --- a/i18n/src/commonMain/moko-resources/bg/plurals.xml +++ b/i18n/src/commonMain/moko-resources/bg/plurals.xml @@ -4,77 +4,58 @@ 1 наличен ъпдейт за разширение %d налични ъпдейта за разширения - Направено за %1$s с %2$s грешка Направено за %1$s с %2$s грешки - %d категория %d категории - - - %1$d страница останала - %1$d страници останали - - %1$s глава %1$s глави - Изтри %1$d изтеглената глава\? Изтри %1$d изтеглените глави\? - Има 1 липсваща глава, източникът липса или е филтриран Има %d липсващи глави, източникът липса или е филтриран - Копирайте %1$d%2$s манга\? Копирайте %1$d%2$sманги\? - Разширението е актуализирано %d разширения са актуализирани - %d преместена манга %d преместени манги - Миграция на %1$d%2$s манга\? Миграция на %1$d%2$s манги\? - За %d заглавие За %d заглавия - %d чакаща актуализация %d чакаща актуализации - %1$d страница %1$d страници - и %1$d повече глава и %1$d повече глави - Една глава е премахната от източника: \n%2$s @@ -84,24 +65,20 @@ \n \nИзтриване на изтеглянето им\? - Почистването е завършено. Премахната е папката %d Почистването е завършено. Премахнати са папките %d - След %1$s минута След %1$s минути - Кешът е изчистен. %d файлът е изтрит Кешът е изчистен. %d файловете са изтрити - Следващата непрочетена глава Следващите %d непрочетени глави - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/bg/strings.xml b/i18n/src/commonMain/moko-resources/bg/strings.xml index e656aeb267..a44cce0d55 100644 --- a/i18n/src/commonMain/moko-resources/bg/strings.xml +++ b/i18n/src/commonMain/moko-resources/bg/strings.xml @@ -543,11 +543,7 @@ Бутони за тема, базирани на корицата През всяка мрежа Не актуализирайте автоматично - Необходими са разрешения за файлове Изтрита категория - TachiyomiJ2K изисква достъп до всички файлове в Android 11, за да изтегля глави, да създава автоматични резервни копия и да чете локална манга. - \n - \nНа следващия екран разрешете \"Разрешаване на достъп за управление на всички файлове.\" Добавете категории Няма намерени съвпадения за текущите ви филтри Покажи всички категории @@ -667,7 +663,6 @@ Автоматични актуализации Приложение за автоматично актуализиране Само през Wi-Fi - TachiyomiJ2K изисква достъп до всички файлове, за да изтегли главите. Докоснете тук, след което разрешете \"Разрешаване на достъп за управление на всички файлове.\" Не можа да се инсталира актуализация Завършена актуализация Вашата библиотека @@ -888,7 +883,6 @@ %1$d пропуснати обновление/я Пропусната, понеже не е започната Изчисти данните на WebView - Форматът RARv5 не се поддържа Грешка: празен URI Подобрява производителността на четеца Данните на WebView изчистени @@ -921,4 +915,4 @@ Изключени категории Инсталатор Работа на заден план - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/bn/plurals.xml b/i18n/src/commonMain/moko-resources/bn/plurals.xml index fd06eb40fb..3cdfe28a11 100644 --- a/i18n/src/commonMain/moko-resources/bn/plurals.xml +++ b/i18n/src/commonMain/moko-resources/bn/plurals.xml @@ -4,37 +4,26 @@ এক্সটেনশন আপডেট পাওয়া গেছে %dটি এক্সটেনশন আপডেট পাওয়া গেছে - %d বিভাগ %d বিভাগসমূহ - - - %1$d পৃষ্ঠা বাকি - %1$d গুলি পৃষ্ঠা বাকি - - %1$s সময়ে %2$s ত্রুটি সম্পন্ন %1$s সময়ে %2$sটি ত্রুটি সম্পন্ন - %1$dটি ডাউনলোড করা অধ্যায় সরাবেন\? ডাউনলোড করা %1$dটি অধ্যায় সরাবেন\? - %1$sটি অধ্যায় %1$s গুলি অধ্যায় - 1 অধ্যায় এড়িয়ে জাছি, হয় উৎসটি অনুপস্থিত বা এটি ফিল্টার করা হয়েছে %d অধ্যায় কিপ করা হচ্ছে, হয় উৎসটি তাদের অনুপস্থিত অথবা সেগুলি ফিল্টার করা হয়েছে - উৎস থেকে একটি অধ্যায় সরানো হয়েছে: \n%2$s @@ -44,64 +33,52 @@ \n \nতাদের ডাউনলোড মুছে ফেলবেন\? - %d টি মাঙ্গা স্থানান্তরিত হয়েছে %d টা মাঙ্গা স্থানান্তরিত হয়েছে - %1$d%2$s মাঙ্গা কপি করবেন\? %1$d%2$s টা মাঙ্গা কপি করবেন\? - ক্যাশে সাফ করা হয়েছে। %d ফাইল মুছে ফেলা হয়েছে ক্যাশে সাফ করা হয়েছে। %d টা ফাইল মুছে ফেলা হয়েছে - পরিষ্কার করা হয়েছে। %d ফোল্ডার সরানো হয়েছে পরিষ্কার করা হয়েছে। %d টা ফোল্ডার সরানো হয়েছে - %d শিরোনামের জন্য %d গুল শিরোনামের জন্য - এবং আরো %1$d টি অধ্যায় এবং আরো %1$d গুল অধ্যায় - %d আপডেট মুলতুবি আছে %d গুলো আপডেট মুলতুবি আছে - এক্সটেনশন আপডেট করা হয়েছে %d টি এক্সটেনশন আপডেট করা হয়েছে - %1$dটা পৃষ্ঠা %1$dটি পৃষ্ঠা - %1$d%2$s মাঙ্গা মাইগ্রেট করবেন\? %1$d%2$s টা মাঙ্গা মাইগ্রেট করবেন\? - %1$s মিনিট পরে %1$s টা মিনিট পরে - পরবর্তী অপঠিত অধ্যায় পরবর্তী %d টি অপঠিত অধ্যায় - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/bn/strings.xml b/i18n/src/commonMain/moko-resources/bn/strings.xml index b132c9ec29..da79a8a07f 100644 --- a/i18n/src/commonMain/moko-resources/bn/strings.xml +++ b/i18n/src/commonMain/moko-resources/bn/strings.xml @@ -576,11 +576,6 @@ বিভাগ যোগ করুন/সম্পাদনা করুন %1$s কে এতে সরান… এতে %1$s যোগ করুন… - ফাইল অনুমতি প্রয়োজন - TachiyomiJ2K-এর জন্য অধ্যায়গুলি ডাউনলোড করতে, স্বয়ংক্রিয় ব্যাকআপ তৈরি করতে এবং স্থানীয় মাঙ্গা পড়তে Android 11-এর সমস্ত ফাইল অ্যাক্সেস করতে হবে। -\n -\nপরবর্তী স্ক্রিনে, \"সমস্ত ফাইল পরিচালনা করতে অ্যাক্সেসের অনুমতি দিন\" সক্ষম করুন।\" - TachiyomiJ2K-এর অধ্যায়গুলি ডাউনলোড করার জন্য সমস্ত ফাইলে অ্যাক্সেস প্রয়োজন। এখানে আলতো চাপুন, তারপর \"সমস্ত ফাইল পরিচালনা করতে অ্যাক্সেসের অনুমতি দিন\" সক্ষম করুন৷\" এক্সটেনশন ইনস্টলার হিসাবে Shizuku ব্যবহার করতে Shizuku ইনস্টল করুন এবং শুরু করুন। শিজুকু চলছে না সঙ্কুচিত ডায়নামিক বিভাগগুলিকে নীচে নিয়ে যাও @@ -887,7 +882,6 @@ %1$s দেখান শিরোনামসমুহ হালনাগাদ এড়িয়ে যান অপঠিত অধ্যায়সহ - RARv5 ধরন সমর্থিত নয় অপঠিত অধ্যায় থাকায় এড়িয়ে যাওয়া হয়েছে FAQ এবং নির্দেশনা সিরিজ কে উপরে তুলুন @@ -902,4 +896,4 @@ পুস্তক সংগ্রহ ডিবাগ তথ্য পটভূমি কার্যকলাপ - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ca/plurals.xml b/i18n/src/commonMain/moko-resources/ca/plurals.xml index 31ea29be14..d8403751b6 100644 --- a/i18n/src/commonMain/moko-resources/ca/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ca/plurals.xml @@ -4,52 +4,42 @@ Hi ha una actualització d’una extensió Hi ha actualitzacions de %d extensions - Voleu eliminar %1$d capítol baixat\? Voleu eliminar %1$d capítols baixats\? - %d categoria %d categories - Fet en %1$s amb %2$s error Fet en %1$s amb %2$s errors - %1$s capítol %1$s capítols - S’ha omès %d capítol. És possible que manqui a la font o que hagi estat filtrat S’han omès %d capítols. És possible que manquin a la font o que hagin estat filtrats - Per a %d títol Per a %d títols - i %1$d capítol més i %1$d capítols més - El següent capítol no llegit Els següents %d capítols no llegits - %1$d pàgina %1$d pàgines - S’ha suprimit un capítol d’aquesta font: \n%2$s @@ -59,14 +49,8 @@ \n \nVoleu eliminar-ne les baixades\? - - - %1$d pàgina restant - %1$d pàgines restants - - Després d’%1$s minut Després de %1$s minuts - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ca/strings.xml b/i18n/src/commonMain/moko-resources/ca/strings.xml index 70e4ce6afa..b4441fe170 100644 --- a/i18n/src/commonMain/moko-resources/ca/strings.xml +++ b/i18n/src/commonMain/moko-resources/ca/strings.xml @@ -452,7 +452,6 @@ Categoria Mou %1$s a… Afegeix %1$s a… - Calen permisos de fitxers Categoria suprimida Vés a la categoria Gestiona la categoria @@ -568,20 +567,15 @@ Sense descripció 5% Cadena d’agent d’usuari per defecte - El format RARv5 no està suportat Capítols baixats Baixa automàticament mentre es llegeix Baixa per avançat Només funciona en elements de la biblioteca i si el capítol actual i el següent ja estan baixats - El TachiyomiJ2K requereix l’accés a tots els fitxers per a poder baixar capítols. Premeu aquí i activeu «Permet l’accés a tots els fitxers». No s’ha pogut instal·lar l’actualització Pàgina del llançament La vostra biblioteca Nova versió beta disponible! Actualització completada - El TachiyomiJ2K requereix l’accés a tots els fitxers en Android 11 per a poder baixar capítols, crear còpies de seguretat automàtiques i llegir mangues locals. -\n -\nA la següent pantalla, activeu «Permet l’accés a tots els fitxers». S’ha omès perquè no cal actualitzar la sèrie La cadena d’agent d’usuari no és vàlida Temps de lectura @@ -735,4 +729,4 @@ Configuració de l’aplicació Informació de depuració Activitat en segon pla - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ceb/strings.xml b/i18n/src/commonMain/moko-resources/ceb/strings.xml index e910d89d3e..3f68b3cc73 100644 --- a/i18n/src/commonMain/moko-resources/ceb/strings.xml +++ b/i18n/src/commonMain/moko-resources/ceb/strings.xml @@ -67,11 +67,6 @@ Tuo ug Wala Baliktad Edge - Gikinahanglan ang mga permiso sa file - Ang TachiyomiJ2K nanginahanglan pag-access sa tanan nga mga file sa Android 11 aron ma-download ang mga kapitulo, paghimo og awtomatikong pag-backup, ug pagbasa sa lokal nga manga. -\n -\nSa sunod nga screen, i-enable ang \"Allow access to manage all files.\" - Ang TachiyomiJ2K nanginahanglan og access sa tanan nga mga file aron ma-download ang mga kapitulo. I-tap dinhi, dayon i-enable ang \"Allow access to manage all files.\" Manhwa Tuo ngadto sa wala Bertikal @@ -332,4 +327,4 @@ Installer Mga entri sa basahonan Kalihokan sa background - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/cs/plurals.xml b/i18n/src/commonMain/moko-resources/cs/plurals.xml index eb9035c525..08318c9b2b 100644 --- a/i18n/src/commonMain/moko-resources/cs/plurals.xml +++ b/i18n/src/commonMain/moko-resources/cs/plurals.xml @@ -5,43 +5,31 @@ Dokončeno za %1$s s %2$s chybami Dokončeno za %1$s s %2$s chybami - %d kategorie %d kategorie %d kategorií - - - Zbývá %1$d stránka - Zbývají %1$d stránky - Zbývá %1$d stránek - - %1$s kapitola %1$s kapitoly %1$s kapitol - Odstranit %1$d staženou kapitolu\? Odstranit %1$d stažené kapitoly\? Odstranit %1$d stažených kapitol\? - a %1$d další kapitolu a %1$d další kapitoly a %1$d dalších kapitol - Je dostupná aktualizace rozšíření Jsou dostupné %d aktualizace rozšíření Je dostupných %d aktualizací rozšíření - Jedna kapitola byla z tohoto zdroje odstraněna: \n%2$s @@ -55,100 +43,84 @@ \n \nChcete je smazat\? - %1$d stránka %1$d stránky %1$d stránek - %d aktualizace čeká %d aktualizace čekají %d aktualizací čeká - Přeskočena %d kapitola, buď chybí ve zdroji nebo byla vyfiltrována Přeskočeny %d kapitoly, buď chybí ve zdroji nebo byly vyfiltrovány Přeskočeno %d kapitol, buď chybí ve zdroji nebo byly vyfiltrovány - Rozšíření bylo aktualizováno %d rozšíření byla aktualizována %d rozšíření bylo aktualizováno - Po %1$s minutě Po %1$s minutách Po %1$s minutách - Migrovat %1$d%2$s mangu\? Migrovat %1$d%2$s mangy\? Migrovat %1$d%2$s mang\? - Vyčištění dokončeno. %d odstraněná složka Vyčištění dokončeno. %d odstraněné složky Vyčištění dokončeno. %d odstraněných složek - Další nepřečtená kapitola Další %d nepřečtené kapitoly Dalších %d nepřečtených kapitol - Kopírovat %1$d%2$s mangu\? Kopírovat %1$d%2$s mangy\? Kopírovat %1$d%2$s mang\? - Mezipaměť vymazána. %d soubor byl odstraněn Mezipaměť vymazána. %d soubory byly odstraněny Mezipaměť vymazána. %d souborů bylo odstraněno - %d zdroj %d zdroje %d zdrojů - %d druh sérií %d druhy sérií %d druhů sérií - Pro %d titul Pro %d tituly Pro %d titulů - %d manga migrována %d manga migrovány %d manga migrováno - %d jazyk %d jazyky %d jazyků - %d status %d statusy %d statusů - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/cs/strings.xml b/i18n/src/commonMain/moko-resources/cs/strings.xml index 60fadafcb3..66393f73bf 100644 --- a/i18n/src/commonMain/moko-resources/cs/strings.xml +++ b/i18n/src/commonMain/moko-resources/cs/strings.xml @@ -536,7 +536,6 @@ Podle čísla kapitoly Podle pořadí zdroje Nezaloženo - Je vyžadováno oprávnění k souborům Nastavit jako výchozí Vlastní info o manze Zdroj není nainstalován @@ -618,9 +617,6 @@ Varování: hromadné stahování může vést k tomu, že zdroje zpomalí a/nebo zablokují Tachiyomi. Klepnutím se dozvíte více. Každé 3 dny Snižuje pruhování barev, ale může mít vliv na výkon - TachiyomiJ2K požaduje přístup ke všem souborům v Android 11, aby mohlo stahovat kapitoly, vytvářet zálohy a načíst lokálně uloženou mangu. -\n -\nNa obrazovce povolte \"Povolit aplikaci přístup ke všem souborům.\" Rozšíření čekající na aktualizaci Čtení %1$s Ve skupině @@ -634,7 +630,6 @@ Oříznout okraje (Strankované) Rozdělit dvojité strany Vyplnit vystřihnuté oblasti - TachiyomiJ2K potřebuje přístup k celému úložišti, aby mohlo stahovat kapitoly. Klikněte sem, poté povolte \"Povolit aplikaci přístup k souborům.\" Zobrazovat přečtené kapitoly v Ve skupině a Všechny Sdílet kombinované strany Chytře (podle strany) @@ -905,7 +900,6 @@ 5% Výchozí řetězec uživatelského agenta Některé jazyky mohou pro správné zobrazení vyžadovat opětovné spuštění aplikace - Formát RARv5 není podporován Všechna přečtená manga Ponechejte mangu s přečtenými kapitolami Automatické stahování při čtení @@ -961,4 +955,4 @@ Nastavení aplikace Ladící informace Činnost na pozadí - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/cv/plurals.xml b/i18n/src/commonMain/moko-resources/cv/plurals.xml index 8a2d53ddea..d22780c11a 100644 --- a/i18n/src/commonMain/moko-resources/cv/plurals.xml +++ b/i18n/src/commonMain/moko-resources/cv/plurals.xml @@ -4,44 +4,32 @@ Хушма валли ҫӗнетӳ пур %d хушма валли ҫӗнетӳ пур - %1$s сыпӑк %1$s сыпӑк - %d пухмӑш %d пухмӑш - - - %1$d эл юлчӗ - %1$d эл юлчӗ - - %1$d тиенӗ сыпӑка катертмелле-и\? %1$d тиенӗ сыпӑксене катертмелле-и\? - %1$s,%2$s йӑнӑшпа тӑвӑннӑ%1$s, %2$s йӑнӑшпа тӑвӑннӑ - 1 минут хыҫҫӑн%1$s минут хыҫҫӑн - Ҫӗнӗ сыпӑксем 1 хайлав валли тупӑннӑҪӗнӗ сыпӑксем %d хайлав валли тупӑннӑ - 1 сыпӑк ҫук %d сыпӑк ҫук - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/cv/strings.xml b/i18n/src/commonMain/moko-resources/cv/strings.xml index e9b4bf29a5..02f280b817 100644 --- a/i18n/src/commonMain/moko-resources/cv/strings.xml +++ b/i18n/src/commonMain/moko-resources/cv/strings.xml @@ -398,7 +398,6 @@ Пур ҫӗрте шыра Пур тиеве катертмелле-и\? Ҫак серилӗх валли пурне те пӑрахӑҫла - Файлсене курма ирӗк памалла Сыпӑк шучӗпе Тиесе илни вӑхӑчӗпе Яланхилле @@ -466,4 +465,4 @@ Сӑрӑ Ларткӑч Вулавӑшри серилӗхсем - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/de/plurals.xml b/i18n/src/commonMain/moko-resources/de/plurals.xml index 460dd138be..bfe82030cd 100644 --- a/i18n/src/commonMain/moko-resources/de/plurals.xml +++ b/i18n/src/commonMain/moko-resources/de/plurals.xml @@ -4,37 +4,26 @@ Entferne %1$d heruntergeladenes Kapitel? Entferne %1$d heruntergeladene Kapitel? - %1$s Kapitel %1$s Kapitel - - - %1$d Seite übrig - %1$d Seiten übrig - - %d Kategorie %d Kategorien - Für %d Titel Für %d Titel - und %1$d weiteres Kapitel und %1$d weitere Kapitel - Erweiterungsaktualisierung verfügbar %d Erweiterungsaktualisierungen verfügbar - Ein Kapitel wurde von der Quelle entfernt: \n%2$s @@ -44,84 +33,68 @@ \n \nDiese heruntergeladenen Dateien löschen\? - %1$d%2$s Manga migrieren? %1$d%2$s Manga migrieren? - %1$d%2$s Manga kopieren? %1$d%2$s Manga kopieren? - %d Manga migriert %d Manga migriert - Zwischenspeicher geleert. %d Datei wurde gelöscht Zwischenspeicher geleert. %d Dateien wurde gelöscht - Aufräumen abgeschlossen. %d Ordner entfernt Aufräumen abgeschlossen. %d Ordner entfernt - Nach %1$s Minute Nach %1$s Minuten - Erledigt in %1$s mit %2$s Fehler Erledigt in %1$s mit %2$s Fehlern - %1$d Seite %1$d Seiten - %d Aktualisierung ausstehend %d Aktualisierungen ausstehend - Erweiterung aktualisiert %d Erweiterungen aktualisiert - %d Kapitel wird übersprungen, da die Quelle dieses entweder nicht besitzt, oder weil es rausgefiltert wurde %d Kapitel werden übersprungen, da die Quelle diese entweder nicht besitzt, oder weil sie rausgefiltert wurden - Nächstes ungelesenes Kapitel Nächste %d ungelesene Kapitel - %d Sprache %d Sprachen - %d Quelle %d Quellen - %d Status %d Status - %d Serientyp %d Serientypen - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/de/strings.xml b/i18n/src/commonMain/moko-resources/de/strings.xml index 69d56fed17..45f7f2e0f4 100644 --- a/i18n/src/commonMain/moko-resources/de/strings.xml +++ b/i18n/src/commonMain/moko-resources/de/strings.xml @@ -762,11 +762,6 @@ Globale Aktualisierungen Eine beliebige Serie öffnen Anzahl der Elemente anzeigen - TachiyomiJ2K benötigt zum Herunterladen von Kapiteln Zugriff auf alle Dateien. Tippen Sie hier und aktivieren Sie dann „Zugriff auf die Verwaltung aller Dateien zulassen.“ - TachiyomiJ2K benötigt Zugriff auf alle Dateien in Android 11 um Kapitel herunterzuladen, automatische Backups zu erstellen und lokale Manga zu lesen. -\n -\nAktivieren Sie im nächsten Bildschirm „Zugriff auf die Verwaltung aller Dateien zulassen.“ - Dateiberechtigungen erforderlich Benachrichtigungsinhalt verbergen Quelle wird nicht unterstützt Ausrichtung @@ -940,7 +935,6 @@ 5% Standard-User-Agent-Text Manche Sprachen benötigen möglicherweise einen Neustart der App zur korrekten Anzeige - Das RARv5-Format wird nicht unterstützt Alle gelesenen Manga Manga mit gelesenen Kapiteln behalten Automatisch während dem Lesen herunterladen @@ -997,4 +991,4 @@ App-Einstellungen Debug-Info Hintergrundaktivität - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/el/plurals.xml b/i18n/src/commonMain/moko-resources/el/plurals.xml index 72b3e20cd0..a26e7b82f1 100644 --- a/i18n/src/commonMain/moko-resources/el/plurals.xml +++ b/i18n/src/commonMain/moko-resources/el/plurals.xml @@ -4,67 +4,50 @@ Διαθέσιμη ενημέρωση επέκτασης %d διαθέσιμες ενημερώσεις επεκτάσεων - και %1$d περισσότερο κεφάλαιο και %1$d περισσότερα κεφάλαια - Για %d τίτλο Για %d τίτλους - %d κατηγορία %d κατηγορίες - - - Απομένει %1$d σελίδα - Απομένουν %1$d σελίδες - - Διαγραφή %1$d κατεβασμένου κεφαλαίου; Διαγραφή %1$d κατεβασμένων κεφαλαίων; - Έγινε σε %1$s με %2$s σφάλμα Έγινε σε %1$s με %2$s σφάλματα - Μετά από %1$s λεπτό Μετά από %1$s λεπτά - Ο καθαρισμός ολοκληρώθηκε. Αφαιρέθηκε %d φάκελος Ο καθαρισμός ολοκληρώθηκε. Αφαιρέθηκαν %d φάκελοι - Η προσωρινή μνήμη διαγράφηκε. %d αρχείο διαγράφηκε Η προσωρινή μνήμη διαγράφηκε. %d αρχεία διαγράφηκαν - %d σειρά μετεγκαταστήθηκε %d σειρές μετεγκαταστήθηκαν - Αντιγραφή %1$d%2$s σειρά; Αντιγραφή %1$d%2$s σειρών; - Μετεγκατάσταση %1$d%2$s σειρά; Μετεγκατάσταση %1$d%2$s σειρών; - Ένα κεφάλαιο καταργήθηκε από την πηγή: \n%2$s @@ -74,54 +57,44 @@ \n \nΔιαγραφή των λήψεων τους; - %1$d σελίδα %1$d σελίδες - %1$s κεφάλαιο %1$s κεφάλαια - %d εκκρεμεί ενημέρωση %d εκκρεμούν ενημερώσεις - Η επέκταση ενημερώθηκε %d επεκτάσεις ενημερώθηκαν - Παραλείπεται %d κεφάλαιο, είτε λείπει από την πηγή είτε έχει φιλτραριστεί Παραλείπονται %d κεφάλαια, είτε λείπουν από την πηγή είτε έχουν φιλτραριστεί - Επόμενο αδιάβαστο κεφάλαιο Επόμενα %d αδιάβαστα κεφάλαια - Τύπος σειράς %d τύποι σειρών - Πηγή %d Πηγές - Κατάσταση %d καταστάσεις - Γλώσσα %d γλώσσες - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/el/strings.xml b/i18n/src/commonMain/moko-resources/el/strings.xml index 2f890850bc..5c93a9d755 100644 --- a/i18n/src/commonMain/moko-resources/el/strings.xml +++ b/i18n/src/commonMain/moko-resources/el/strings.xml @@ -741,11 +741,6 @@ Κατά αριθμό κεφαλαίου Κατά σειρά πηγής Χωρίς σελιδοδείκτη - Το TachiyomiJ2K απαιτεί πρόσβαση σε όλα τα αρχεία για να κατεβάσετε κεφάλαια. Πατήστε εδώ και στη συνέχεια, ενεργοποιήστε την επιλογή \"Να επιτρέπεται η πρόσβαση στη διαχείριση όλων των αρχείων.\" - Το TachiyomiJ2K απαιτεί πρόσβαση σε όλα τα αρχεία στο Android 11 για να κατεβάζει κεφάλαια, να δημιουργεί αυτόματα αντίγραφα ασφαλείας και να διαβάζει τις τοπικά αποθηκευμένες σειρές. -\n -\nΣτην επόμενη οθόνη, ενεργοποιήστε την επιλογή \"Να επιτρέπεται η πρόσβαση στη διαχείριση όλων των αρχείων.\" - Απαιτείται άδεια πρόσβασης αρχείων Tako A Calmer You (Δυναμικό) Προειδοποίηση @@ -905,7 +900,6 @@ Βιολετί 5% Προεπιλεγμένη συμβολοσειρά πράκτορα χρήστη - Η μορφή RARv5 δεν υποστηρίζεται Ορισμένες γλώσσες ενδέχεται να απαιτούν επανεκκίνηση της εφαρμογής για να εμφανιστούν σωστά Διατήρηση σειρών με αναγνωσμένα κεφάλαια Όλες οι σειρές που διαβάστηκαν @@ -964,4 +958,4 @@ Εφαρμογή Συνιστάται να επιτρέψετε τις ειδοποιήσεις για να διατηρείται την βιβλιοθήκη σας και την εφαρμογή σας ενημερωμένες. Κοινοποίηση εξωφύλλου - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/eo/plurals.xml b/i18n/src/commonMain/moko-resources/eo/plurals.xml index 8ff7da8010..b20e14c445 100644 --- a/i18n/src/commonMain/moko-resources/eo/plurals.xml +++ b/i18n/src/commonMain/moko-resources/eo/plurals.xml @@ -1,42 +1,31 @@ - - Cetere %1$d paĝo - Cetere %1$d paĝoj - - Forigi %1$d elŝutitan ĉapitron\? Forigi %1$d elŝutitajn ĉapitrojn\? - 1 ĉapitro %1$s ĉapitroj - %d kategorio%d kategorioj - Farita en %1$s kun %2$s eraro Farita en %1$s kun %2$s eraroj - Mankas 1 ĉapitron Mankas %d ĉapitrojn - Post 1 minutoPost %1$s minutoj - Por 1 titoloPor %d titoloj - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/eo/strings.xml b/i18n/src/commonMain/moko-resources/eo/strings.xml index 446eef6ec5..8721108a43 100644 --- a/i18n/src/commonMain/moko-resources/eo/strings.xml +++ b/i18n/src/commonMain/moko-resources/eo/strings.xml @@ -169,7 +169,6 @@ Servoj Ĉi tiu kromaĵo ne estas de la oficiala Tachiyomi kromaĵlisto. Sekura ekrano - Agordi kiel kovrilo Eraro dumo kovrila kunigado Eraro dum kovrila konservado @@ -362,4 +361,4 @@ Devigi malŝlosi Griza Biblioteka kontribuoj - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/es/plurals.xml b/i18n/src/commonMain/moko-resources/es/plurals.xml index 7ca91a260c..b7c186236d 100644 --- a/i18n/src/commonMain/moko-resources/es/plurals.xml +++ b/i18n/src/commonMain/moko-resources/es/plurals.xml @@ -5,37 +5,26 @@ %d actualizaciones de extensiónes disponibles %d actualizaciones de extensiones disponibles - %d categoría %d categorías %d categorías - - - %1$d página restante - %1$d páginas restantes - %1$d páginas restantes - - ¿Eliminar %1$d capítulo descargado\? ¿Eliminar %1$d capítulos descargados\? ¿Eliminar %1$d capítulos descargados\? - y %1$d capítulo más y %1$d capítulos más y %1$d capítulos más - Para %d título Para %d títulos Para %d títulos - Un capítulo ha sido eliminado de la fuente: \n%2$s @@ -49,106 +38,89 @@ \n \n¿Eliminar capítulos descargados\? - Después de %1$s minuto Después de %1$s minutos Después de %1$s minutos - Limpieza realizada. Carpeta %d eliminada Limpieza realizada. Carpetas %d eliminadas Limpieza realizada. Carpetas %d eliminadas - Completada en %1$s con %2$s error Completada en %1$s con %2$s errores Completada en %1$s con %2$s errores - %d serie migrada %d series migradas %d series migradas - ¿Copiar la serie %1$d%2$s\? ¿Copiar las series %1$d%2$s\? ¿Copiar las series %1$d%2$s\? - ¿Migrar la serie %1$d%2$s\? ¿Migrar las series %1$d%2$s\? ¿Migrar las series %1$d%2$s\? - %1$s capítulo %1$s capítulos %1$s capítulos - Caché borrada. Se ha eliminado el archivo %d Caché borrada. Se han eliminado archivos %d Caché borrada. Se han eliminado archivos %d - %1$d página %1$d páginas %1$d páginas - %d actualización pendiente %d actualizaciones pendientes %d actualizaciones pendientes - Extensión actualizada %d extensiones actualizadas %d extensiones actualizadas - Se omite %d capítulo, o bien falta en la fuente o ha sido filtrado Se omiten %d capítulos, o bien faltan en la fuente o han sido filtrados Se omiten %d capítulos, o bien faltan en la fuente o han sido filtrados - El siguiente capítulo sin leer Los siguientes %d capítulos sin leer Los siguientes %d capítulos sin leer - %d fuente %d fuentes %d fuentes - %d Lenguaje %d lenguajes %d Otros lenguajes - %d estado %d estados %d estados - %d tipos de serie %d tipos de series %d tipos de series - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/es/strings.xml b/i18n/src/commonMain/moko-resources/es/strings.xml index 11efa99f35..f0289e1efa 100644 --- a/i18n/src/commonMain/moko-resources/es/strings.xml +++ b/i18n/src/commonMain/moko-resources/es/strings.xml @@ -760,11 +760,6 @@ Actualizaciones globales Abrir una serie aleatoria Mostrar el número de elementos - TachiyomiJ2K requiere acceso a todos los archivos para descargar capítulos. Toque aquí y, acto seguido, active \"Acceso a todos los archivos.\" - TachiyomiJ2K requiere acceso a todos los archivos en Android 11 para descargar los capítulos, crear las copias de seguridad automáticas y leer las series locales. -\n -\nEn la siguiente pantalla, activa \"Permitir el acceso para gestionar todos los archivos\" - Se necesita el permiso de archivos Advertencia Orientación Orientación predeterminada @@ -935,7 +930,6 @@ Violeta 5% Nombre del navegador a usar («user agent») - La app no soporta el formato RARv5 Algunos idiomas pueden requerir un reinicio de la aplicación para mostrarse correctamente Guardar las entradas con los capítulos leídos Todas las series leídas @@ -994,4 +988,4 @@ Información sobre la depuración Actividad en segundo plano Compartir la portada - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/eu/plurals.xml b/i18n/src/commonMain/moko-resources/eu/plurals.xml index afc55abfd9..9dded181a7 100644 --- a/i18n/src/commonMain/moko-resources/eu/plurals.xml +++ b/i18n/src/commonMain/moko-resources/eu/plurals.xml @@ -4,27 +4,22 @@ Kendu kapitulu %1$d\? Kendu %1$d kapitulu\? - Kapitulu bat saltatzen, iturria falta da edo iragazia izan da %d kapitulu saltatzen, iturria falta da edo iragaziak izan dira - %d kategoria %d kategoriak - %1$d orrialdea %1$d orrialdeak - %d manga migratu da %d manga migratu dira - Kapitulu bat iturritik kendua izan da: \n%2$s @@ -34,69 +29,52 @@ \n \nHaien deskargak ezabatu\? - Migratu manga %1$d%2$s\? Migratu %1$d%2$s manga\? - Kopiatu manga %1$d%2$s\? Kopiatu %1$d%2$s manga\? - Garbiketa egina. Karpeta %d kendu da Garbiketa egina. %d karpeta kendu dira - Cachea garbitu da. Fitxategi %d ezabatu da Cachea garbitu da. %d fitxategi ezabatu dira - Minutu %1$s-en ondoren %1$s minuturen ondoren - Luzapena eguneratu da %d Luzapen eguneratu dira - %d tituluarentzako %d tituluentzako - eta kapitulu %1$d gehiago eta %1$d kapitulu gehiago - eguneraketa %d egiteke %d eguneraketa egiteke - Luzapenaren eguneraketa eskuragarri %d Luzapenen eguneraketak eskuragarri - %1$s-n egin da errore %2$s-ekin %1$s-n egin da %2$s errorerekin - %1$s kapitulu %1$s kapituluak - - - Orri %1$d geratzen da - %1$d orri geratzen dira - - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/eu/strings.xml b/i18n/src/commonMain/moko-resources/eu/strings.xml index 1ab5ee28b1..910d3d2233 100644 --- a/i18n/src/commonMain/moko-resources/eu/strings.xml +++ b/i18n/src/commonMain/moko-resources/eu/strings.xml @@ -1,8 +1,5 @@ - TachiyomiJ2K-k Android 11-kako fitxategi guztietara sarrera behar du kapituluak jaitsi, segurtasun kopia automatikoak sortu, eta manga era lokalean irakurtzeko. -\n -\nHurrengo pantailan, aukeratu \"Baimendu fitxategi guztietarako sarrera.\" %1$s eguneratze-zerrendara gehitzen Kategoria ezabatua Kategoria berria sortu @@ -67,8 +64,6 @@ Etiketa Desblokeatu Desblokeatu liburutegira sartzeko - Fitxategi baimenak behar dira - TachiyomiJ2K-k fitxategi guztietara sartzeko baimena behar du kapituluak jaisteko. Egin klik hemen, eta aukeratu \"Baimendu fitxategiak kudeatzeko sarrera.\" Manga Manhwa Manhua @@ -861,4 +856,4 @@ Baztertutako kategoriak Instalatzailea Atzeko planoko ekintzak - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fa/strings.xml b/i18n/src/commonMain/moko-resources/fa/strings.xml index a0bc89601d..58e92453a8 100644 --- a/i18n/src/commonMain/moko-resources/fa/strings.xml +++ b/i18n/src/commonMain/moko-resources/fa/strings.xml @@ -303,7 +303,6 @@ آغاز نشده کامیک مانهوای - محلی پشتیبان گیری خودکار فایل پشتیبان نامعتبر است @@ -444,10 +443,6 @@ پاک کردن سابقه سابقه پاک شد آیا مطمئن هستید؟ تمام سابقه از دست خواهد رفت. - اجازه دسترسی به فایل - تاچیومی J2K نیازمند دسترسی به همه فایل ها در اندوید 11 برای دانلود قسمت ها، ایجاد پشتیبان گیری خودکار، و خواندن مانگاهای محلی( مانگا های موجود در دستگاه شما) است. -\n -\nدر صفحه بعدی، اجازه \"مجاز بودن دسترسی به مدیریت همه پرونده‌ها\" را بدهید. مدیریت اعلان‌ها رد شده راهنمای شروع @@ -501,7 +496,6 @@ نصف کردن خودکار عکس های بلند مناطق قابل لمس نام دسته نمی تواند خالی باشد - TachiyomiJ2K برای دانلود فصل ها نیاز به دسترسی به همه فایل ها دارد. روی اینجا ضربه بزنید، سپس «Allow access to management all files» را فعال کنید. در حال بروز رسانی %1$s به روز رسانی تکمیل شد نکات جستجو به صورت دوره ای نمایش داده می شود. برای جستجوی، پیشنهاد را به مدت طولانی فشار دهید. @@ -541,4 +535,4 @@ نصب کننده کتابخانه ورودی‌ها فعالیت در پس زمینه - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fi/plurals.xml b/i18n/src/commonMain/moko-resources/fi/plurals.xml index 7cd1316e99..2e09ea4171 100644 --- a/i18n/src/commonMain/moko-resources/fi/plurals.xml +++ b/i18n/src/commonMain/moko-resources/fi/plurals.xml @@ -1,56 +1,41 @@ - Laajennospäivitys saatavilla %d laajennospäivitystä saatavilla - %d kategoria %d kategoriaa - - - %1$d sivu jäljellä - %1$d sivua jäljellä - - Poistetaanko %1$d ladattu luku\? Poistetaanko %1$d ladattua lukua\? - Nimikkeelle %d Nimikkeille %d - Valmistui %1$s virheitä löytyi %2$s Valmistui %1$s virheitä löytyi %2$s - Välimuisti tyhjennetty. %d tiedosto on poistettu Välimuisti tyhjennetty. %d tiedostoa on poistettu - %d manga siirretty %d mangaa siirretty - Kopioi %1$d%2$s manga\? Kopioi %1$d%2$s mangasta\? - Siirrä %1$d%2$s manga\? Siirrä %1$d%2$s mangaa\? - Luku on poistettu lähteestä: \n%2$s @@ -60,39 +45,32 @@ \n \nPoistetaanko niiden lataukset\? - ja %1$d lisää luku ja %1$d lisää lukua - %1$s luku %1$s lukua - Siivous tehty. Poistettu %d kansio Siivous tehty. Poistettu %d kansiota - %1$s minuutin jälkeen %1$s minuutin jälkeen - %1$d sivu %1$d sivua - Seuraava lukematon luku Seuraavat %d lukematonta lukua - 1 puuttuva luku %d puuttuvaa lukua - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fi/strings.xml b/i18n/src/commonMain/moko-resources/fi/strings.xml index 27a23de06d..f7427294d6 100644 --- a/i18n/src/commonMain/moko-resources/fi/strings.xml +++ b/i18n/src/commonMain/moko-resources/fi/strings.xml @@ -706,7 +706,6 @@ Merkitse useat luvut luetuiksi Peruuta kaikki tälle sarjalle Sovelluksen pikavalinnat - Tiedoston käyttöoikeudet vaaditaan Piilota ilmoitusten sisältö Lukuun ottamatta: %s Lähdettä ei tueta @@ -782,7 +781,6 @@ Automaattinen lataus luetessa %1$d sarjaa joita ei olla lisätty kirjastoon tietokanassa Parannetut palvelut - RARv5-muoto ei ole tuettu Korkea Alhaisin Ladattua kuvaa ei voitu jakaa @@ -817,4 +815,4 @@ Poissuljetut kategoriat Asentaja Taustatoiminta - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fil/plurals.xml b/i18n/src/commonMain/moko-resources/fil/plurals.xml index 4911fc8ee5..eeaf928852 100644 --- a/i18n/src/commonMain/moko-resources/fil/plurals.xml +++ b/i18n/src/commonMain/moko-resources/fil/plurals.xml @@ -1,81 +1,61 @@ - %1$s kabanata %1$s (na) kabanata - Available na ang pag-update ng extension Available na ang %d na mga update sa extension - %d kategorya %d (na) kategorya - - - %1$d pahina na lang - %1$d (na) pahina na lang - - Tanggalin ang %1$d na-download na kabanata\? Tanggalin ang %1$d (na) na-download na kabanata\? - Na-restore sa loob ng %1$s na may %2$s error Na-restore sa loob ng %1$s na may %2$s (na) error - Nalinis na ang cache. Binura ang %d file Nalinis na ang cache. Binura ang %d (na) file - %d nakabinbing update %d (na) nakabinbing update - Para sa %d serye Para sa %d (na) serye - at %1$d pang kabanata at %1$d pang mga kabanata - Na-update na ang extension Na-update na ang %d na mga extension - %1$d pahina %1$d (na) pahina - Nilaktawan ang %d na kabanata, maaaring ito ay wala sa source o na-filter ang mga ito Nilaktawan ang %d na mga kabanata, maaaring wala sa source o na-filter ang mga ito - Pagkatapos ng %1$s minuto Pagkatapos ng %1$s (na) minuto - Ilipat ang %1$d manga %2$s\? Ilipat ang %1$d (na) manga %2$s\? - Tinanggal sa source ang kabanata: \n%2$s @@ -85,44 +65,36 @@ \n \nBurahin ang mga na-download\? - Nailipat na ang %d manga Nailipat na ang %d (na) manga - Kopyahin ang %1$d manga %2$s\? Kopyahin ang %1$d (na) manga %2$s\? - Tapos na ang paglilinis. Inalis ang %d folder Tapos na ang paglilinis. Inalis ang %d (na) folder - Susunod na hindi pa nababasa na kabanata Susunod na %d di pa nababasa na kabanata - %d uri ng serye %d mga uri ng serye - %d na pinagkukunan %d na mga pinagmumulan - %d na katayuan %d mga katayuan - %d wika %d na mga wika - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fil/strings.xml b/i18n/src/commonMain/moko-resources/fil/strings.xml index 7822b1ecd2..7552093ea2 100644 --- a/i18n/src/commonMain/moko-resources/fil/strings.xml +++ b/i18n/src/commonMain/moko-resources/fil/strings.xml @@ -570,11 +570,6 @@ Panlahatang pag-update Rine-refresh ang mga cover sa aklatan at habang nag-a-update Kusang i-refresh ang mga cover - Kinakailangan ng Yokai ng access sa lahat ng mga file para maka-download ng mga kabanata. I-tap ito, tapos pakibuksan ang \"Payagan ang pamamahala ng lahat ng file.\" - Ang Yokai ay nangangailangan ng access sa lahat ng mga file sa Android 11 upang mag-download ng mga kabanata, gumawa ng mga awtomatikong pag-backup, at magbasa ng lokal na manga. -\n -\nSa susunod na screen, paganahin ang \"Payagan ang pag-access upang pamahalaan ang lahat ng mga file.\" - Kailangan ng permiso sa file Kapag paalpabetong mag-aayos, ayusin nang binabalewala ang mga article (a, an, the) sa simula ng mga pamagat Ayusin nang binabalewala ang mga article Lumipat nang dalawahang pahina @@ -904,7 +899,6 @@ Lila 5% Default na string ng user agent - Di suportado ang format na RARv5 Maaaring mangailangan ang ilang wika ng muling pag-restart ng app upang maipakita ito Patuloy na magbasa ng mga kabanata Mga nabasang manga @@ -960,4 +954,100 @@ Mga setting ng app Impormasyon sa pag-debug Gawaing background - + Iba pang opsiyon + Mag-navigate pataas + Custom na threshold sa hardware bitmap + Umiiral na ang repo na ito! + Bago lamang + I-restart ang aplikasyon + Maligayang Pagdating! + Pumili tayo ng ilang default. Maaari mong palaging baguhin ang mga bagay-bagay sa ibang pagkakataon sa mga setting. + Magsimula + Pumili ng folder kung saan mag-imbak ang %1$s ng mga na-download ng kabanata, mga backup, at higit pa. \n \nInirerekomenda ang isang nakalaang folder. \n \nNapiling folder: %2$s + Pumili ng folder + Nag-a-update mula sa isang mas lumang bersyon at hindi sigurado kung ano ang pipiliin? Sumangguni sa Tachiyomi upgrade na seksyon sa Mihon storage guide para sa higit pang impormasyon. + Gabay sa storage + Kinakailangan + Opsyonal ngunit inirerekomenda + Pahintulot sa pag-install ng mga app + Upang ma-install ang app sa mga update. + Pahintulot sa mga abiso + Maabisuhan para sa mga update sa aklatan at higit pa. + Paggamit ng baterya sa background + Payagan + Walang nakatakdang lokasyon ng storage + Imbalidong lokasyon: %s + Imbalidong lokasyon + Pahina %1$d ng %2$d + Magbukas ng random na serye (Pangkahalatan) + Paganahin ang pagkilos na pag-swipe ng kabanata + Mga update + Ipakita ang queue sa pag-download + Buksan ang huling nabasang kabanata + Gawi ng pag-long tap sa Kamakailan + Buksan ang pangkalahatang paghanap + Gawi ng pag-log tap sa Maghanap + Legasiya + Nagbibigay-daan sa mga extension na ma-install nang walang mga prompt ng user at nagbibigay-daan sa mga awtomatikong pag-update para sa mga device sa ilalim ng Android 12 + Default + Hindi pa ipinapatupad ang legacy installer, kasalukuyang bumabalik sa PackageInstaller (Default) + Maaaring basahin ng mga mapanganib na extension ang anumang nakatagong kredensyal sa pag-log in o magsagawa ng arbitrary code. \n \nSa pamamagitan ng pagtitiwala sa extension na ito, tinatanggap mo ang mga panganib na ito. + Bawiin ang mga pinagkakatiwalaang hindi kilalang extension + Bawiin lahat ng mga pinagkatiwalaang mga extension? + I-crop ang mga border (Pahabang strip) + Buksan ang mga setting ng legasiyang cutout + Sa mga device na mas luma sa Android 9.0, kailangan mong itakda nang manu-mano ang mga setting ng cutout sa pamamagitan ng mga setting ng sistema + Ipakita ang nilalaman sa lugar ng cutout + Lumikha ng folder base sa titulo ng manga + Default (%d) + I-double tap para mag-zoom + I-share ang cover + Datos at storage + Lokasyon ng storage + Paggamit ng storage + Na magagamit: %1$s / Kabuuan: %2$s + Kasali ang mga sensitibong setting (hal. mga tracker login token) + Huling awtomatikong na-back up: %s + I-print ang detalyadong mga log sa system log (nakakabawas sa performance ng app) + Mode sa pag-debug + Verbose na pagla-log + Maghanap sa mga external na storage para sa mga entry + Mga Repo ng Extension + Magdagdag ng repo + Idagdag ang repo + Di-wastong URL ng repo + Hindi ka pa nagdaragdag ng anumang mga repo. + Tanggalin ang repo? + Gusto mo bang tanggalin ang repo na \"%s\"? + Palitan + Umiiral na ang Signing Key Fingerprint + Ang repository na %1$s ay may magkaparehong Signing Key Fingerprint sa %2$s. \nKung ito ay inaasahan, %2$s ang papalitan, kung hindi naman ay makipag-ugnayan sa tagapamahala ng iyong repo. + Open source na repo + Hindi mabuksan ang url + Ilagay sa ibaba ang serye + Awtomatikong idagdag ang ID + I-tap dito para sa tulong sa Cloudflare + Kinakailangan ng app ang WebView upang gumana ito + Nabigong makakuha ng patuloy na pag-access ng folder. Ang app ay magkaroon ng di-inaasahang pagkilos. + Bumalik + I-refresh + Ilapat + Sumulong + Webtoon + Internal na error: %s + SFW + NSFW + Uri ng nilalaman + Isang Hindi Inaasahang Error ang Naganap + Nagkaroon ng hindi inaasahang error ang %s. Iminumungkahi naming i-screenshot mo ang mensaheng ito, i-dump ang mga crash log, at pagkatapos ay ibahagi ito sa isang GitHub Issue. + I-refresh + Pahabang strip + Buksan ang mga extension / menu ng paglilipat + Ma-iwasan ang mga hadlang sa mahahabang pag-update ng aklatan, pag-download, at pag-restore ng mga backup. + Napili + Kung naglo-load ang reader ng isang blangkong imahe ay unti-unting bawasan ang threshold.\nNapili: %s + Walang napili + I-save ang mga pahina sa magkakaibang folder + Custom na profile sa display + Maaaring maayos ang isyu sa mga na-download na kabanata na magkasalungat sa isa\'t isa kapag pareho sila ng pangalan + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fr/plurals.xml b/i18n/src/commonMain/moko-resources/fr/plurals.xml index 537f5e5a77..d5486db060 100644 --- a/i18n/src/commonMain/moko-resources/fr/plurals.xml +++ b/i18n/src/commonMain/moko-resources/fr/plurals.xml @@ -1,78 +1,60 @@ - Mise à jour d\'extension disponible %d mises à jour des extensions disponibles %d mises à jour des extensions disponibles - et %1$d autre chapitre et %1$d autres chapitres et %1$d autres chapitres - Pour le titre du %d Pour les titres des %d Pour les titres des %d - Supprimer %1$d chapitre téléchargé \? Supprimer %1$d chapitres téléchargés \? Supprimer %1$d chapitres téléchargés \? - %d catégorie %d catégories %d catégories - - - %1$d page restante - %1$d pages restantes - %1$d pages restantes - - Après %1$s minute Après %1$s minutes Après %1$s minutes - Nettoyage terminé. %d dossier supprimé Nettoyage terminé. %d dossiers supprimés Nettoyage terminé. %d dossiers supprimés - Cache effacé. %d fichier a été supprimé Cache effacé. %d fichiers ont été supprimés Cache effacé. %d fichiers ont été supprimés - %d manga migré %d mangas migrés %d mangas migrés - Copier le manga %1$d%2$s \? Copier les mangas %1$d%2$s \? Copier les mangas %1$d%2$s \? - Migrer le manga %1$d%2$s \? Migrer les mangas %1$d%2$s \? Migrer les mangas %1$d%2$s \? - Un chapitre a été retiré de la source : \n%2$s @@ -86,70 +68,59 @@ \n \nSupprimer les téléchargements \? - Effectuée en %1$s avec %2$s erreur Effectuée en %1$s avec %2$s erreurs Effectuée en %1$s avec %2$s erreurs - %1$s chapitre %1$s chapitres %1$s chapitres - %1$d page %1$d pages %1$d pages - %d mise à jour en attente %d mises à jour en attente %d mises à jour en attente - Extension mise à jour %d extensions mises à jour %d extensions mises à jour - %d chapitre a été sauté, soit la source ne l\'a pas, soit il a été filtré %d chapitres ont été sautés, soit la source ne les a pas, soit ils ont été filtrés %d chapitres ont été sautés, soit la source ne les a pas, soit il ont été filtrés - Chapitre suivant non lu Les %d suivants non lus Les %d suivants non lus - %d type de série %d types de série %d types de série - %d source %d sources %d sources - %d état %d états %d états - %d langue %d langues %d langues - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/fr/strings.xml b/i18n/src/commonMain/moko-resources/fr/strings.xml index 920addd442..63ae623199 100644 --- a/i18n/src/commonMain/moko-resources/fr/strings.xml +++ b/i18n/src/commonMain/moko-resources/fr/strings.xml @@ -740,10 +740,6 @@ Surfaces coupées du bloc Action d\'un appui long sur le filtre par Catégorie Tout annuler pour cette série - Pour télécharger des chapitres, TachiyomiJ2K nécessite l\'accès à tous les fichiers. Appuyez ici, puis activez « Autoriser l\'accès pour gérer tous les fichiers.» - Sous Android 11, TachiyomiJ2K nécessite l\'accès à tous les fichiers pour pouvoir télécharger des chapitres, créer des sauvegardes automatiques et lire des manga locaux. -\n -\nSur l\'écran suivant, activez « Autoriser l\'accès pour gérer tous les fichiers.» Inclure : %s Cela forcera le recalcul du cache de téléchargement. Utile si vous avez modifié des fichiers téléchargés en dehors de l\'application et que vous souhaitez que l\'application les récupère Rafraichir le cache de téléchargement @@ -767,7 +763,6 @@ Mode sombre noir pur Alignement de l\'icône de navigation latérale Fraises au chocolat - Autorisations de fichiers requises Sélectionner par défaut Par date de téléversement Non mis en favoris @@ -939,7 +934,6 @@ 5% Agents utilisateurs par défaut Certains langages peuvent nécessiter un redémarrage de l\'application pour s\'afficher correctement - Le format RARv5 n\'est pas supporté Téléchargement anticipé pendant la lecture Chapitres téléchargés Stat @@ -992,4 +986,4 @@ Entrées de la bibliothèque Informations de débogage Activité en arrière-plan - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/gl/strings.xml b/i18n/src/commonMain/moko-resources/gl/strings.xml index a549007f45..91188e0dd7 100644 --- a/i18n/src/commonMain/moko-resources/gl/strings.xml +++ b/i18n/src/commonMain/moko-resources/gl/strings.xml @@ -228,12 +228,7 @@ Capítulo %1$s Todos os capítulos lidos Scanlators - TachiyomiJ2K require acceso a tódolos arquivos de Android 11 para descargar capítulos, facer copias de seguridade automáticas e ler manga en local. -\n -\nNa seguinte pantalla, marca \"Permitir acceso á xestión de tódolos arquivos.\" - Requírense permisos de arquivo Non gardado nos marcadores - TachiyomiJ2K require acceso a tódolos arquivos para descargar capítulos, Preme eiquí, e despóis marca \"Permitir acceso á xestión de tódolos arquivos.\" Autor Move %1$s a… Selección inversa @@ -288,7 +283,6 @@ Borráronse as entradas Baixar capítulos novos Elexir a imaxe de portada - O formato RARv5 non está soportado Oimitiuse porque hai capítulos sin ler Actualizar o seguemento Erros @@ -494,4 +488,4 @@ Borrar o historial dos elementos que non estean gardados na túa biblioteca Información de depuración Actividade en segundo plano - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hi/plurals.xml b/i18n/src/commonMain/moko-resources/hi/plurals.xml index 837756dda0..0d82bf18d0 100644 --- a/i18n/src/commonMain/moko-resources/hi/plurals.xml +++ b/i18n/src/commonMain/moko-resources/hi/plurals.xml @@ -1,53 +1,39 @@ - एक्सटेंशन अपडेट उपलब्ध%d एक्सटेंशन अपडेट उपलब्ध - %1$s में %2$s त्रुटि के साथ किया गया%1$s में %2$s त्रुटियों के साथ किया गया - %d श्रेणी %d श्रेणियाँ - %1$s मिनट के बाद%1$s मिनट के बाद - %d अध्याय को छोड़ा जा रहा है, या तो स्रोत में यह नहीं है या इसे फ़िल्टर कर दिया गया है %d अध्यायों को छोड़ा जा रहा है, या तो स्रोत उन्हें याद कर रहा है या उन्हें फ़िल्टर कर दिया गया है - अध्याय %1$s अध्यायों %1$s - - - %1$d पृष्ठ बाकि - %1$d पृष्ठ बाकि - - %1$d डाउनलोड अध्याय को निकालें\? %1$d डाउनलोड अध्यायों को निकालें\? - %d शीर्षक के लिए %d शीर्षकों के लिए - अगला अपठित अध्याय अगले %d अपठित अध्याय - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hi/strings.xml b/i18n/src/commonMain/moko-resources/hi/strings.xml index 2c5688ea6d..ea20c9328c 100644 --- a/i18n/src/commonMain/moko-resources/hi/strings.xml +++ b/i18n/src/commonMain/moko-resources/hi/strings.xml @@ -442,13 +442,8 @@ मानहुआ कॉमिक नए अध्याय - फाइल की अनुमति चाहिए - ताचियोमी को एंड्राइड ११ में फाइल अनुमति चाहिए ताकि वो चैप्टर डाउनलोड कर सके, आटोमेटिक बैकअप बना सके और लोकल माँगा पढ़ सके -\n -\nअगले स्क्रीन पर, सारे फाइल को एक्सेस करने की अनुमति को सेलेक्ट करे मानह्वा शुरू नहीं हुआ हैं - ताचियोमी ज २ क को सारे फाइल को एक्सेस करने की अनुमति चाहिए ताकि वो नए चैप्टर डाउनलोड कर सके. यहाँ पे क्लिक करके \" सारे फाइल को एक्सेस करने की अनुमति दे \" चालु हैं लाइब्रेरी तक पहुंचने के लिए अनलॉक करें अंतिम पढ़ा अध्याय %1$s @@ -545,7 +540,6 @@ ५% डाउनलोड किए गए अध्याय आपके फ़िल्टर के लिए कोई मिलान नहीं - RARv5 प्रारूप समर्थित नहीं है आगे डाउनलोड करें पढ़ते समय ऑटो डाउनलोड करे होल्ड लिस्ट में @@ -587,4 +581,4 @@ लाइब्रेरी के आइटम डीबग जानकारी पृष्ठभूमि गतिविधि - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hr/plurals.xml b/i18n/src/commonMain/moko-resources/hr/plurals.xml index 1a51514d75..41fdf79c9d 100644 --- a/i18n/src/commonMain/moko-resources/hr/plurals.xml +++ b/i18n/src/commonMain/moko-resources/hr/plurals.xml @@ -1,66 +1,50 @@ - %1$s poglavlje %1$s poglavlja %1$s poglavlja - Dostupna je %d nova verzija proširenja Dostupne su %d nove verzije proširenja Dostupoe je %d novih verzija proširenja - %d kategorija %d kategorije %d kategorija - - - %1$d preostala stranica - %1$d preostale stranice - %1$d preostalih stranica - - Ukloniti %1$d preuzeto poglavlje\? Ukloniti %1$d preuzeta poglavlja\? Ukloniti %1$d preuzetih poglavlja\? - i još %1$d naslov i još %1$d naslova i još %1$d naslova - Za %d naslov Za %d naslova Za %d naslova - Brisanje je gotovo. Uklonjena je %d mapa Brisanje je gotovo. Uklonjene su %d mape Brisanje je gotovo. Uklonjeno je %d mapa - Nakon %1$s minute Nakon %1$s minute Nakon %1$s minuta - Obavljeno u %1$s s %2$s greškom Obavljeno u %1$s s %2$s greške Obavljeno u %1$s s %2$s grešaka - Iz izvora je uklonjeno jedno poglavlje: \n%2$s @@ -72,82 +56,69 @@ \n%2$s \nŽeliš li izbrisati njihova preuzimanja\? - Predmemorija je izbrisana. %d datoteka je izbrisana Predmemorija je izbrisana. %d datoteke su izbrisane Predmemorija je izbrisana. %d datoteka je izbrisano - %d serija migrirana %d serije migrirane %d serija migrirano - Kopirati %1$d%2$s seriju\? Kopirati %1$d%2$s serije\? Kopirati %1$d%2$s serija\? - Migrati %1$d%2$s seriju\? Migrati %1$d%2$s serije\? Migrati %1$d%2$s serija\? - %1$d stranica %1$d stranice %1$d stranica - %d aktualiziranje na čekanju %d aktualiziranja na čekanju %d aktualiziranja na čekanju - %d proširenje aktualizirano %d proširenja aktualizirana %d proširenja aktualizirano - Preskače se %d poglavlje. Ne postoji u izvoru ili je filtrirano Preskaču se %d poglavlja. Ne postoje u izvoru ili su filtrirana Preskače se %d poglavlja. Ne postoje u izvoru ili su filtrirana - Sljedeće nepročitano poglavlje Sljedeća %d nepročitana poglavlja Sljedećih %d nepročitanih poglavlja - %d vrsta serije %d vrste serije %d vrsta serije - %d stanje %d stanja %d stanja - %d izvor %d izvora %d izvora - %d jezik %d jezika %d jezika - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hr/strings.xml b/i18n/src/commonMain/moko-resources/hr/strings.xml index c1d6ed84f4..f4b8c28558 100644 --- a/i18n/src/commonMain/moko-resources/hr/strings.xml +++ b/i18n/src/commonMain/moko-resources/hr/strings.xml @@ -706,10 +706,6 @@ Označi raspon poglavlja kao pročitana Prečaci programa Prekini sve za ovu seriju - TachiyomiJ2K zahtijeva pristup svim datotekama u Androidu 11 za preuzimanje poglavlja, za stvaranje automatskih sigurnosnih kopija i za čitanje lokalnih serija. -\n -\nU sljedećem ekranu aktiviraj „Dopusti pristup za upravljanje svim datotekama”. - Potrebne su dozvole za datoteke Globalna aktualiziranja Otvaranje slučajne serije Prikaži broj elemenata @@ -719,7 +715,6 @@ Po imenu izvora Po broju poglavlja Nije zabilježeno - TachiyomiJ2K zahtijeva pristup svim datotekama za preuzimanje poglavlja. Dodirni ovdje, zatim uključi „Dopusti pristup za upravljanje svim datotekama”. Neki proizvođači imaju dodatna programska ograničenja koja onemogućuju pozadinske usluge. Ova web-stranica sadrži daljnje informacije o tome kako to popraviti. Ovo će prisiliti ponovno izračunavanje predmemorije preuzimanja. Korisno, ako su preuzimanja promijenjena izvan ovog programa i ako želiš da ih program preuzme Upozorenje @@ -904,7 +899,6 @@ Otvori u programu 5 % Neki jezici mogu zahtijevati ponovno pokretanje aplikacije za ispravan prikaz - RARv5 format nije podržan Sve pročitane serije Standardni izraz korisničkog agenta Čuvaj unose s pročitanim poglavljima @@ -962,4 +956,4 @@ Postavke aplikacije Informacije otklanjanja grešaka Aktivnost u pozadini - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hu/plurals.xml b/i18n/src/commonMain/moko-resources/hu/plurals.xml index 7888aabf78..1fa80616bc 100644 --- a/i18n/src/commonMain/moko-resources/hu/plurals.xml +++ b/i18n/src/commonMain/moko-resources/hu/plurals.xml @@ -1,58 +1,43 @@ - Egy új bővítményfrissítés érhető el%d bővítményfrissítés érhető el - %d kategória %d kategóriák - - - %1$d oldal hátra - %1$d oldalak hátra - - %1$s fejezet %1$s fejezetek - Eltávolítasz %1$d letöltött fejezetet\? Eltávolítasz %1$d letöltött fejezeteket\? - Befejezve %1$s alatt, %2$s hibával Befejezve %1$s alatt, %2$s hibával - 1 perc után%1$s percek után - %d-nak/nek%d-nak/nek - és még %1$d fejezet és még %1$d fejezet - Következő olvasatlan fejezet Következő %d olvasatlan fejezet - %d fejezet kihagyása, hiányzik a forrás, vagy ki lett szűrve %d fejezet kihagyása, hiányoznak a források, vagy ki lettek szűrve - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/hu/strings.xml b/i18n/src/commonMain/moko-resources/hu/strings.xml index 8f64bf7ce0..7a7cd51b75 100644 --- a/i18n/src/commonMain/moko-resources/hu/strings.xml +++ b/i18n/src/commonMain/moko-resources/hu/strings.xml @@ -504,13 +504,10 @@ Elkezdett Nem bejelentkezett trackerek: Művész - Fájl engedély szükséges - A TachiyomiJ2K a fejezetek letöltéséhez hozzáférést igényel az összes fájlhoz. Koppintson ide, majd engedélyezze a \"Hozzáférés engedélyezése az összes fájl kezeléséhez\" Az %1$s áthelyezése ide… %1$s hozzáadása a… Feloldás a könyvtárhoz való hozzáféréshez Felold - A TachiyomiJ2K a fejezetek letöltéséhez, automatikus biztossági mentés létrehozásához és a hely manga olvasásához hozzáférést igényel az összes fájlhoz. A következő képernyőn engedélyeze \"Hozzáférés engedélyezése az összes fájl kezeléséhez\" Nyelv jelvények Nincs elkezdve Nincs találat a jelenlegi filterre @@ -535,7 +532,6 @@ Sorozat típus 5% Alap hálózati kliens szöveg - RARv5 formátum nem támogatót Letölzozz fejezetek Kezdése gomb elrejtése Több könyvtár beállítás @@ -569,4 +565,4 @@ Könyvtár bejegyzések Debug információ Háttér aktivitás - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/in/plurals.xml b/i18n/src/commonMain/moko-resources/in/plurals.xml index 3f8834211e..0d01847b55 100644 --- a/i18n/src/commonMain/moko-resources/in/plurals.xml +++ b/i18n/src/commonMain/moko-resources/in/plurals.xml @@ -1,102 +1,75 @@ - Terdapat %d perbaharuan ekstensi - dan %1$d bab lain - Untuk %d judul - %d kategori - - - %1$d halaman tersisa - - Hapus %1$d bab yang diunduh\? - Selesai dalam %1$s dengan %2$s kesalahan - %1$s bab - %1$s bab telah dihapus dari sumber: \n%2$s \n \nHapus unduhan\? - %d manga dimigrasi - Salin manga %1$d%2$s\? - Pembersihan selesai. %d folder telah dihapus - Cache telah dibersihkan. %d berkas telah dihapus - %1$d halaman - Setelah %1$s menit - Migrasi %1$d%2$s manga\? - Pembaruan %d ditunda - %d - Melewati %d bab, entah sumbernya hilang atau telah difilter - Selanjutnya chapter %d yang belum dibaca - %d jenis seri - %d sumber - %d status - %d bahasa - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/in/strings.xml b/i18n/src/commonMain/moko-resources/in/strings.xml index 1a12821c17..3e59525baa 100644 --- a/i18n/src/commonMain/moko-resources/in/strings.xml +++ b/i18n/src/commonMain/moko-resources/in/strings.xml @@ -706,7 +706,6 @@ Aktifkan %s Tidak ada Alternatif Ditemukan Tidak ada bab yang ditemukan, manga ini tidak dapat digunakan untuk migrasi - Perizinan berkas diperlukan Beberapa pabrikan mempunyai batasan aplikasi tambahan yang mematikan layanan latar belakang. Website ini memiliki info lebih lanjut untuk memperbaikinya. Hal ini akan memaksa cache unduhan untuk mengkalkulasi ulang. Berguna jika Anda memodifikasi unduhan di luar aplikasi ini dan ingin aplikasi ini untuk membacanya Beberapa ekstensi masih mengingatkan untuk dipasang terlebih dulu. @@ -757,10 +756,6 @@ Tampilkan jumlah item Pembaruan selesai Tak ditandai - TachiyomiJ2K membutuhkan akses semua berkas untuk mengunduh bab. Ketuk di sini, lalu izinkan \"Izinkan akses untuk mengatur semua berkas.\" - TachiyomiJ2K membutuhkan akses ke semua berkas di Android 11 untuk mengunduh bab, membuat cadangan otomatis, dan memuat manga lokal. -\n -\nPada layar selanjutnya, izinkan \"Izinkan akses untuk mengatur semua berkas\" Pembatasan: %1$s Hapus %1$s dari %2$s dan tambahkan %3$s Dipasang baru-baru ini @@ -905,7 +900,6 @@ 5% String agen pengguna default Beberapa bahasa mungkin memerlukan peluncuran ulang aplikasi untuk ditampilkan dengan benar - Format RARv5 tidak didukung Semua manga yang dibaca Simpan manga dengan membaca chapter Unduh otomatis ketika sedang membaca @@ -959,4 +953,7 @@ Entri pustaka Info debug Aktivitas dibelakang layar - + Selamat datang! + Opsi lebih lanjut + Segarkan + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/it/plurals.xml b/i18n/src/commonMain/moko-resources/it/plurals.xml index 377f9a40b8..286ab7078b 100644 --- a/i18n/src/commonMain/moko-resources/it/plurals.xml +++ b/i18n/src/commonMain/moko-resources/it/plurals.xml @@ -1,66 +1,50 @@ - Aggiornamento estensione disponibile %d estensioni hanno aggiornamenti disponibili %d estensioni hanno aggiornamenti disponibili - %d categoria %d categorie %d categorie - - - %1$d pagina rimasta - %1$d pagine rimaste - %1$d pagine rimaste - - Rimuovere %1$d capitolo scaricato\? Rimuovere %1$d capitoli scaricati\? Rimuovere %1$d capitoli scaricati\? - Dopo un minuto Dopo %1$s minuti Dopo %1$s minuti - Eliminazione completata. Rimossa la cartella %d Eliminazione completata. Rimosse le cartelle %d Eliminazione completata. Rimosse le cartelle %d - Cache libera. Il file %d è stato rimosso Cache libera. I file %d sono stati rimossi Cache libera. I file %d sono stati rimossi - Una serie migrata %d serie migrate %d serie migrate - Copiare la serie %1$d%2$s\? Copiare le serie %1$d%2$s\? Copiare le serie %1$d%2$s\? - Migrare la serie %2$s\? Migrare le serie %1$d%2$s\? Migrare le serie %1$d%2$s\? - Un capitolo è stato rimosso dalla sorgente: \n%2$s @@ -74,82 +58,69 @@ \n \nEliminare i loro download\? - e un altro capitolo e %1$d altri capitoli e %1$d altri capitoli - Per un titolo Per %d titoli Per %d titoli - Completato in %1$s con %2$s errore Completato in %1$s con %2$s errori Completato in %1$s con %2$s errori - %1$s capitolo %1$s capitoli - %1$d pagina %1$d pagine %1$d pagine - %d aggiornamento in attesa %d aggiornamenti in attesa %d aggiornamenti in attesa - Estensione aggiornata %d estensioni aggiornate %d estensioni aggiornate - %d capitolo saltato, la fonte non ce l\'ha o è stato filtrato %d capitoli saltati, la fonte non li ha o sono stati filtrati %d capitoli saltati, la fonte non li ha o sono stati filtrati - Il prossimo capitolo non letto I prossimi %d capitoli non letti I prossimi %d capitoli non letti - Un tipo di serie %d tipi di serie %d tipi di serie - Fonte %d fonti %d fonti - Status %d stati %d stati - Una lingua %d lingue %d lingue - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/it/strings.xml b/i18n/src/commonMain/moko-resources/it/strings.xml index 1240577768..454e1333bd 100644 --- a/i18n/src/commonMain/moko-resources/it/strings.xml +++ b/i18n/src/commonMain/moko-resources/it/strings.xml @@ -749,11 +749,6 @@ Nascondi badge non letti Apri una serie casuale Mostra il numero di oggetti - TachiyomiJ2K richiede accesso a tutti i file per scaricare i capitoli. Fai tap qui, poi fornisci l\'autorizzazione per \"Accesso a tutti i file.\" - TachiyomiJ2K richiede accesso ai file per scaricare capitoli, creare backup automatici e leggere serie in locale. -\n -\nAlla prossima schermata, fornisci l\'autorizzazione per \"Accesso a tutti i file.\" - Permessi per la gestione dei file richiesti Disattiva %s Attiva %s Selezione @@ -938,7 +933,6 @@ 5% Stringa user agent predefinita del browser Alcune lingue potrebbero richiedere un riavvio dell\'app per esserve visualizzate in maniera corretta - Il formato RARv5 non è supportato Tieni le voci con capitoli letti Tutte le serie lette Capitoli scaricati @@ -996,4 +990,4 @@ Attività in background Consente l\'installazione delle estensioni senza richieste da parte dell\'utente e abilita gli aggiornamenti automatici per i dispositivi con Android 12 Condividi copertina - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/iw/plurals.xml b/i18n/src/commonMain/moko-resources/iw/plurals.xml index 786dab276e..3302df72d6 100644 --- a/i18n/src/commonMain/moko-resources/iw/plurals.xml +++ b/i18n/src/commonMain/moko-resources/iw/plurals.xml @@ -1,73 +1,57 @@ - קטגורייה אחת שתי קטגוריות %d קטגוריות - זמין עדכון לתוסף זמינים עדכונים ל-%d תוספים זמינים עדכונים ל-%d תוספים - הושלם ב %1$s עם שגיאה אחת הושלם ב %1$s עם שתי שגיאות הושלם ב %1$s עם %2$s שגיאות - לאחר דקה אחת לאחר שתי דקות לאחר %1$s דקות - הסר %1$d פרק מההורדות\? הסר %1$d פרקים מההורדות\? הסר %1$d פרקים מההורדות\? הסר %1$d פרקים מההורדות\? - %1$s פרק %1$s פרקים %1$s פרקים %1$s פרקים - - - דף %1$d נותר - %1$d דפים נותרו - %1$d דפים נותרו - %1$d דפים נותרו - - עבור כותר אחד עבור שני כותרים עבור %d כותרים - דולג פרק אחד, המקור חסר או שהוא סונן החוצה דולגו שני פרקים, המקור חסר או שהם סוננו החוצה דולגו %d פרקים, המקור חסר או שהם סוננו החוצה - הפרק הבא שלא נקרא שני הפרקים הבאים שלא נקראו %d הפרקים הבאים שלא נקראו - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/iw/strings.xml b/i18n/src/commonMain/moko-resources/iw/strings.xml index eb364ef8be..a45df8318d 100644 --- a/i18n/src/commonMain/moko-resources/iw/strings.xml +++ b/i18n/src/commonMain/moko-resources/iw/strings.xml @@ -1,10 +1,5 @@ - נדרשת הרשאת גישה לקבצים - טאצ\'יומי J2K דורש גישה לכל הקבצים של אנדרואיד 11 כדי להוריד פרקים, צור גיבויים אוטומטיים, וקרא מנגה מקומית. -\n -\nבמסך הבא, אפשר \"תן גישה לכל הקבצים.\" - TachiyomiJ2K דורש גישה לכל הקבצים כדי להוריד פרקים. הקש כאן ולאחר מכן הפעל את \"אפשר גישה לניהול כל הקבצים.\" לעולם לא החדש ביותר הבא @@ -426,7 +421,6 @@ מדריך נדידת מקורות החרגה: %s %1$d עדכונים נכשלו - הפורמט RARv5 לא נתמך מוסיף %1$s לעדכון %1$s כבר נמצא בתור העדכון קטגוריה חדשה @@ -525,4 +519,4 @@ מחק את היסטוריית הפריטים שאינם שמורים בספריה שלך מידע דיבוג פעילות רקע - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ja/plurals.xml b/i18n/src/commonMain/moko-resources/ja/plurals.xml index dfbcdc245d..ee2c5859b5 100644 --- a/i18n/src/commonMain/moko-resources/ja/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ja/plurals.xml @@ -1,101 +1,74 @@ - %1$d件のダウンロードした章を削除してもよろしいですか? - %1$s章 - - - 残り%1$dページ - - %d カテゴリー - %d件のタイトル - とさらに%1$d章 - %d件のアップデートが待機中 - %d件の拡張機能をアップデートしました - %d件の拡張機能の更新が利用可能 - %1$dページ - ソースには存在しないか、フィルターによって排除されたため、%d章がスキップされました - 次の未読の%d章 - 第%1$s章はソースから消去されています: \n%2$s \nダウンロードを削除しますか? - %1$d%2$sのシリーズを移行しますか? - %1$d%2$sのシリーズをコピーしますか? - %dのシリーズを移行しました - %1$sで完成済み %2$s件のエラーが発生しました - キャッシュをクリアしました。%dファイルが削除されました - クリーンアップ完了。%dフォルダーが消去されました - %d件のシリーズ タイプ - %d件のソース - %d件のステータス - %d件の言語 - %1$s分後 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ja/strings.xml b/i18n/src/commonMain/moko-resources/ja/strings.xml index 0f96aeb4e4..2e83cb41dd 100644 --- a/i18n/src/commonMain/moko-resources/ja/strings.xml +++ b/i18n/src/commonMain/moko-resources/ja/strings.xml @@ -1,10 +1,5 @@ - ファイルアクセス権限が必要 - 章のダウンロード、自動バックアップの作成及びローカルマンガシリーズの閲覧を行うには、TachiyomiJ2KはAndroid 11での全ファイルへのアクセス権が必要です。 -\n -\n次の画面で「全てのファイルの管理を許可」を有効にしてください。 - 章をダウンロードするには、TachiyomiJ2Kは全ファイルへのアクセス権が必要です。こちらをタップして「全てのファイルの管理を許可」を有効にしてください。 ようこそ! いくつかのデフォルトを選択しましょう。これらの設定は後から変更できます。 始める @@ -74,7 +69,6 @@ 並び順 章が見つかりません ページが見つかりません - フォーマットRARv5は未対応です 全てのダウンロードを削除しますか? 削除する章がありません ソースでの順位に基づく @@ -1075,4 +1069,4 @@ %s が予期しないエラーに遭遇しました。このメッセージをスクリーンショットにしてクラッシュログを保存し、GitHub Issue に共有することをお勧めします。 アプリを再起動 更新 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/jv/strings.xml b/i18n/src/commonMain/moko-resources/jv/strings.xml index 2729483bfb..1b251f7c71 100644 --- a/i18n/src/commonMain/moko-resources/jv/strings.xml +++ b/i18n/src/commonMain/moko-resources/jv/strings.xml @@ -1,11 +1,6 @@ - Ijin berkas dibutuhake Manga - TachiyomiJ2K mbutuhake akses menyang kabeh file ing Android 11 kanggo ndownload bab, nggawe serep otomatis, lan maca manga lokal. -\n -\n Ing layar sabanjure, aktifake \"Allow akses kanggo ngatur kabeh file.\" - TachiyomiJ2K mbutuhake akses menyang kabeh file kanggo ngundhuh bab. Tutul ing kene, banjur aktifake \"Allow akses kanggo ngatur kabeh file.\" Manhwa Judhul Bab @@ -214,4 +209,4 @@ Awas Pemasang Entri perpustakaan - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ka/strings.xml b/i18n/src/commonMain/moko-resources/ka/strings.xml index 180dc383f5..52409233d1 100644 --- a/i18n/src/commonMain/moko-resources/ka/strings.xml +++ b/i18n/src/commonMain/moko-resources/ka/strings.xml @@ -320,8 +320,7 @@ გამოსახულებსი ჩატვირთვის შეცდომა მარქაფი უკვე მიმდინარეობს გაიგეთ, რატომ - ფაილის აუცილებელი წვდომები ნაცრისფერი ბიბლიოთეკის ჩანაწერები ისტორიიდან წაიშლება ჩანაწერები, რომლებიც თქვენს ბიბლიოთეკაში არაა შენახული - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/kk/plurals.xml b/i18n/src/commonMain/moko-resources/kk/plurals.xml index 0682967128..e143e74bbf 100644 --- a/i18n/src/commonMain/moko-resources/kk/plurals.xml +++ b/i18n/src/commonMain/moko-resources/kk/plurals.xml @@ -1,48 +1,35 @@ - %d санат %d санат - %1$s минуттан кейін%1$s минуттан кейін - %1$s дегеннен кейін %2$s қателікпен орындалды %1$s дегеннен кейін %2$s қателікпен орындалды - Келесі оқылмаған тарау Келесі %d оқылмаған тарау - %1$s тарау %1$s тарау - %1$d жүктелген тарауды жою керек пе\? %1$d жүктеген тарауды жою керек пе\? - - - %1$d бет қалды - %1$d бет қалды - - Кеңейту үшін жаңарту бар%d кеңейту үшін жаңарту бар - Дереккөзі жоқ немесе сүзілген %d тарау өткізіліп жіберілді Дереккөзі жоқ немесе сүзілген %d тарау өткізіліп жіберілді - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/kk/strings.xml b/i18n/src/commonMain/moko-resources/kk/strings.xml index 86bfede22b..a3641b2c1b 100644 --- a/i18n/src/commonMain/moko-resources/kk/strings.xml +++ b/i18n/src/commonMain/moko-resources/kk/strings.xml @@ -175,10 +175,6 @@ Сұрыптау Бастау Тоқтату - Файлдарға рұқсат керек - TachiyomiJ2K тарауларды жүктеп алу, автоматты сақтық көшірме жасау және жергілікті манга оқу үшін Android 11 жүйесіндегі барлық файлдарға кіруді қажет етеді. -\n -\nКелесі экранда «Барлық файлдарды басқаруға рұқсат беру» опциясын қосыңыз. Жолақты азайтады, бірақ өнімділікке әсер етуі мүмкін Өшірулі Соңғы оқылған тарау @@ -376,7 +372,6 @@ Күйі Файл таңдайтын қолданба табылмады Сурет сақталды - RARv5 пішімі қолжетімсіз %1$d жаңарту сәтсіз өтті Тарих жойылды Жоспарланған @@ -394,7 +389,6 @@ Мұқаба жаңартылды Кітапхана жаңаруда Жүктелген бет бөлінбеді - Тараулар жүктеу үшін TachiyomiJ2K-ге барлық файлдарды қарауға рұқсат беріңіз. Мынаны басып, \"Барлық файлдарды басқаруға рұқсат ету\" дегенді қосыңыз %1$s дегенді қайда жылжыту Барлық тараулар оқылды %2$d тараудың %1$d @@ -489,4 +483,4 @@ Кітапханада жоқ жазбалардың тарихын жою Дебаг туралы ақпарат Аялық белсенділік - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/km/plurals.xml b/i18n/src/commonMain/moko-resources/km/plurals.xml index 8cbe16f088..5349e5e618 100644 --- a/i18n/src/commonMain/moko-resources/km/plurals.xml +++ b/i18n/src/commonMain/moko-resources/km/plurals.xml @@ -1,23 +1,15 @@ - %d ថ្នាក់ - លុបភាគ %1$d ដែលបានទាញយកចោល\? - %1$s ភាគ - - - នៅសល់%1$d ទំព័រទៀត - - %d ភាគបន្ទាប់ដែលមិនទាន់អាន - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/km/strings.xml b/i18n/src/commonMain/moko-resources/km/strings.xml index 841aeefe13..f4f32ec1ca 100644 --- a/i18n/src/commonMain/moko-resources/km/strings.xml +++ b/i18n/src/commonMain/moko-resources/km/strings.xml @@ -118,10 +118,6 @@ ចែករំលែក ឈប់ខ្ទាស់ មើលភាគ - ត្រូវការការអនុញ្ញាត - តាឈិយ៉ូមិJ2K ត្រូវការaccessរាល់ហ្វល់ទាំងអស់នៅក្នុងអ៊េនដ្រយដ៏១១ដើម្បីទាញយកភាគ បង្កើតbackupsដោយស្វ័យប្រវត្ត និងអានម៊េងហ្គាដែលមានស្រាប់នៅក្នុងទូរសព្ទ។ -\n -\nនៅអេក្រង់បន្ទាប់​ បើក \"អនុញ្ញាតឲ្យaccessដើម្បីគ្រប់គ្រងរាល់ហ្វាល់ទាំងអស់\" ម៉ាន់វ៉ា ម៉ាន់ហួរ កូមិច @@ -130,7 +126,6 @@ វិចិត្រករ មិនទាន់ចាប់ផ្ដើម អាន - តាឈិយ៉ូមិJ2Kត្រូវការaccessរាល់ហ្វាល់ទាំងអស់ដើម្បីទាញយកភាគ ចុចត្រង់នេះបន្ទាប់មក \"អនុញ្ញាតឲ្យaccessដើម្បីគ្រប់គ្រងរាល់ហ្វាល់ទាំងអស់\" កំពុងដំណើរការ សង្ខេបរឿង ឡើង @@ -175,7 +170,6 @@ ទម្រង់ភាគមិនត្រឹមត្រួវ កំពុងតែធ្វើបច្ចុប្បន្នភាពបណ្ណាល័យ បានជ្រើសរើស: %1$d - ទម្រង់RARv5មិនត្រូវបានទទួលយកទេ គ្មានភាគដែលឲ្យលុបបានទេ ថ្នាក់ %1$s មាននៅក្នុងជួរ @@ -242,4 +236,4 @@ តម្រៀបតម្រងឡើងវិញ មាន %d មិនទាន់អាន មើលរឿងណាមួយ - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ko/plurals.xml b/i18n/src/commonMain/moko-resources/ko/plurals.xml index 1c5e70bd8d..eca63c90d8 100644 --- a/i18n/src/commonMain/moko-resources/ko/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ko/plurals.xml @@ -1,102 +1,75 @@ - 소요 시간: %1$s, 발생한 오류: %2$s - %1$s분 후 - %d 카테고리 - %d개의 확장 앱 업데이트가 있습니다 - %d개의 제목 - %1$s장 - 다운로드한 챕터 %1$d개를 제거하시겠습니까\? - - - %1$d페이지 남음 - - 그리고 %1$d개 화 남았다 - %d 확장 앱이 업데이트됨 - %d 업데이트 보류 중 - 읽지 않은 다음 %d 회차 - 소스에 존재하지 않거나 필터링되어 있는 %d개의 회차를 건너뛰었습니다 - %1$d 페이지 - %1$s 회차가 원본에서 제거되었습소스: \n%2$s \n \n다운로드를 삭제하시겠습니까\? - %d 만화가 마이그레이션됨 - %1$d%2$s 만화를 마이그레이션하시겠습니까\? - %1$d%2$s 만화를 복사하시겠습니까\? - 캐시가 지워졌습니다. %d 파일이 삭제되었습니다 - 정리가 완료되었습니다. 제거된 %d 폴더 - %d 언어 - "%d 시리즈 형식" - %d 소스 - %d 상태 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ko/strings.xml b/i18n/src/commonMain/moko-resources/ko/strings.xml index 458b5e4257..780356ee7d 100644 --- a/i18n/src/commonMain/moko-resources/ko/strings.xml +++ b/i18n/src/commonMain/moko-resources/ko/strings.xml @@ -469,12 +469,6 @@ 읽지 않은 작품 진행 중 새 장 - 안드로이드의 파일 사용권한이 필요합니다 - 안드로이드 정책 변화에 따라 앱은 사용자에게 파일 접근권한을 요청합니다. -\nTachiyomiJ2K는 안드로이드11의 모든 파일에 접근하여, 챕터를 다운로드하고, 자동백업을 만들며, 휴대전화 저장소에 저장된 오프라인 만화를 읽을 수 있습니다. -\n -\n이에, 다음 화면에서 TachiyomiJ2K에 대한 \"모든 파일에 대한 접근권한\"을 허용해 주십시오. - TachiyomiJ2K는 챕터를 다운로드하기 위해 모든 파일에 액세스해야 합니다. 여기를 탭한 다음에 “모든 파일을 관리하기 위한 액세스 허용\"을 활성화해주십시오. 한국만화 중국만화 잠금 해제 @@ -617,7 +611,6 @@ 검색 팁이 주기적으로 표시됩니다. 추천 검색어를 길게 눌러 검색하세요. 5% 기본 사용자 에이전트 문자열 - RARv5 포맷은 지원되지 않습니다 이중 페이지 페이지 미리 로드 크기 유니폼 커버 @@ -960,4 +953,4 @@ 서재 항목 디버그 정보 백그라운드 활동 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/lt/strings.xml b/i18n/src/commonMain/moko-resources/lt/strings.xml index 5808acad7e..89e298cc8f 100644 --- a/i18n/src/commonMain/moko-resources/lt/strings.xml +++ b/i18n/src/commonMain/moko-resources/lt/strings.xml @@ -289,7 +289,6 @@ Rūšiuoti pagal Skyrių nerasta Puslapių nerasta - RARv5 formatas nepalaikomas Visada rodyti skyrių perėjimus Šoninis spaudinėjimas Nėra @@ -474,4 +473,4 @@ Bibliotekos įrašai Ištrinkite įrašų istoriją, kurie nėra išsaugoti jūsų bibliotekoje Fono veikla - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/lv/plurals.xml b/i18n/src/commonMain/moko-resources/lv/plurals.xml index 5eaa15d1b0..36325015b4 100644 --- a/i18n/src/commonMain/moko-resources/lv/plurals.xml +++ b/i18n/src/commonMain/moko-resources/lv/plurals.xml @@ -1,63 +1,48 @@ - Pabeigts %1$s ar %2$s kļūdām Pabeigts %1$s ar %2$s kļūdu Pabeigts %1$s ar %2$s kļūdām - un vēl %1$d nodaļas un vēl %1$d nodaļa un vēl %1$d nodaļas - - - Palikušas %1$d lapas - Palikusi %1$d lapa - Palikušas %1$d lapas - - %1$s nodaļas %1$s nodaļa %1$s nodaļas - Pēc %1$s minūtēmPēc %1$s minūtesPēc %1$s minūtēm - %d kategoriju%d kategorija%d kategorijas - Pieejami %d paplašinājumi atjaunināšanaiPieejams %d paplašinājums atjaunināšanaiPieejami %d paplašinājumi atjaunināšanai - Priekš %dPriekš %dPriekš %d - Izlaisti %d nodaļu, vai nu to nav avotā, vai arī tie ir izfiltrēti Izlaists %d nodaļa, vai nu tā nav avotā, vai arī tā ir izfiltrēta Izlaistas %d nodaļas, vai nu tās nav avotā, vai arī tās ir izfiltrētas - Nākamā nelasītā nodaļa Nākamā nelasītā nodaļa Nākamās %d nelasītas nodaļas - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/lv/strings.xml b/i18n/src/commonMain/moko-resources/lv/strings.xml index 500a97f5aa..cd6db93e1c 100644 --- a/i18n/src/commonMain/moko-resources/lv/strings.xml +++ b/i18n/src/commonMain/moko-resources/lv/strings.xml @@ -166,8 +166,6 @@ Nosaukums Nav sākts Komikss - - Invertētas skāriena zonas Neviens Izsekots @@ -508,7 +506,6 @@ Bibliotēka pēdējo reizi atjaunināta: %s Lejupielādēt uz priekšu Logrīks nav pieejams, ja ir iespējota lietotņu bloķēšana - RARv5 formāts netiek atbalstīts Populārs Skatiet savus nesen atjauninātos bibliotēkas ierakstus Nederīga lietotāja agent virkne @@ -532,4 +529,4 @@ Dzēst vēsturi ierakstiem, kas nav saglabāti jūsu bibliotēkā Atkļūdošanas informācija Fona darbība - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/mn/strings.xml b/i18n/src/commonMain/moko-resources/mn/strings.xml index 2ce5cb2406..01178bd9d8 100644 --- a/i18n/src/commonMain/moko-resources/mn/strings.xml +++ b/i18n/src/commonMain/moko-resources/mn/strings.xml @@ -1,12 +1,7 @@ - Файлийн зөвшөөрөл шаардлагатай Комик - \"TachiyomiJ2K\" нь Андройд 11-ийн бүх файлыг хэрэглэх зөвшөөрөл авч байж утсан дээрээс тань манга уншиж, манга татах боломжтой. -\n -\nДараа гарч ирэх хэсэгт, бүх файлийг удирдахыг зөвшөөрнө үү. Эхлээгүй - TachiyomiJ2K бүх файлуудыг ашиглах зөвшөөрөлтэй байж манга татах боломжтой. Энд дараад, бүх файлыг удирдах зөвшөөрөл олгоно уу. Манхуа Манхуа Ашиглахын тулд онгойлгоно уу @@ -45,7 +40,6 @@ Бүлгийг устгалаа. Бүлэг олдсонгүй Ямар ч бүлэг олдсонгүй - \"RARv5\" формат дэмжихгүй Бүлгийн бичиглэл буруу Эрэмбэлэх Хуудас олдсонгүй diff --git a/i18n/src/commonMain/moko-resources/ms/plurals.xml b/i18n/src/commonMain/moko-resources/ms/plurals.xml index f76c53e464..e5af82913e 100644 --- a/i18n/src/commonMain/moko-resources/ms/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ms/plurals.xml @@ -1,101 +1,74 @@ - %d kemas kini sambungan tersedia - dan %1$d lebih - Untuk %d - - - %1$d halaman yang tinggal - - Buang %1$d bab yang dimuat turun\? - Selepas %1$s - Pembersihan selesai. Di buang %d folder - Chache di bersihkan. %d fail telah di - %d manga di - Salin %1$d%2$s manga\? - Pindah %1$d%2$s manga\? - %1$s bab telah dibuang daripada sumber: \n%2$s \nPadam muat turun\? - %d kategori - Selesai dalam %1$s dengan %2$s ralat - %1$s bab - %1$d halaman - Melangkau %d bab, sama ada sumber tidak mempunyai bab tersebut, atau ia ditapis keluar - %d kemas kini belum selesai - %d sambungan dikemas kini - %d bab tidak dibaca seterusnya - %d jenis siri - %d sumber - %d status - %d bahasa - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ms/strings.xml b/i18n/src/commonMain/moko-resources/ms/strings.xml index 3e709d78c5..4b375306e2 100644 --- a/i18n/src/commonMain/moko-resources/ms/strings.xml +++ b/i18n/src/commonMain/moko-resources/ms/strings.xml @@ -706,10 +706,6 @@ Pintasan apl Tandakan julat bab sebagai belum dibaca Tandakan julat bab sebagai dibaca - TachiyomiJ2K memerlukan akses kepada semua fail di Android 11 untuk memuat turun bab, membuat sandaran automatik, dan membaca manga lokal. -\n -\nPada skrin berikutnya, hidupkan \"benarkan akses mengurus semua fail.\" - Kebenaran fail diperlukan Sumber tidak disokong Sembunyikan kandungan pemberitahuan Sesetengah pengeluar ada sekatan tambahan pada aplikasi yang akan menghentikan perkidmatan latar belakang. Laman web ini ada maklumat cara membaikinya. @@ -805,7 +801,6 @@ Zon ketik Mengikut urutan sumber Lambang bahasa - TachiyomiJ2K memerlukan akses kepada semua fail untuk memuat turun bab. Tap sini, kemudian hidupkan \"benarkan akses untuk mengurus semua fail.\" Papar garisan luar sekeliling muka hadapan Versi beta baharu tersedia! scanlator @@ -816,7 +811,6 @@ kemas kini siap 5% Untaian ejen pengguna lalai - Format RARv5 tidak disokong Tidak ditanda buku Papar %1$s Tambah label @@ -950,4 +944,4 @@ Entri pustaka Maklumat nyahpepijat Aktiviti latar belakang - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/my/plurals.xml b/i18n/src/commonMain/moko-resources/my/plurals.xml index ec069b8248..d1f94ec90e 100644 --- a/i18n/src/commonMain/moko-resources/my/plurals.xml +++ b/i18n/src/commonMain/moko-resources/my/plurals.xml @@ -1,19 +1,12 @@ - အမျိုးအစား %d ခု - - - စာမျက်နှာ %1$d ခုကျန်သေးသည် - - %1$s ပိုင်း - ဒေါင်းလုဒ်ဆွဲထားတဲ့အပိုင်း %1$d ပိုင်းကိုဖျက်ပစ်မှာလား - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/my/strings.xml b/i18n/src/commonMain/moko-resources/my/strings.xml index 809228610c..a22ace7ad5 100644 --- a/i18n/src/commonMain/moko-resources/my/strings.xml +++ b/i18n/src/commonMain/moko-resources/my/strings.xml @@ -153,7 +153,6 @@ ပြောင်းပြန်ရွေးချယ်ရန် အရံ ဒေါင်းလုဒ်အမှတ်အသားများ - ဖိုင်ခွင့်ပြုချက်လိုအပ်သည် အသစ်ထည့်ထားသော ဖျက်စရာအပိုင်းမရှိပါ ဒေါင်းထားတာတွေအားလုံးကို ဖျက်မှာလား\? @@ -173,9 +172,6 @@ … အဖြစ် ပြမည် အမျိုးအစားခုန်ကျော်သည့် ခလုတ်ကို ဖျောက်မည် နောက်ထပ် Library ဆက်တင်များ - Android 11 တွင် အပိုင်းများဒေါင်းလုဒ်လုပ်ရန်၊ အလိုအလျောက် backup လုပ်ရန်နှင့် စက်တွင်း Manga များဖတ်ရန် TachiyomiJ2K အား စက်တွင်းဖိုင်အားလုံးရယူခွင့်ပေးဖို့ လိုအပ်ပါသည်။ -\n -\nနောက်စခရင်သို့ ရောက်ရှိပါက \"ဖိုင်အားလုံးစီမံပိုင်ခွင့်\" ကို ဖွင့်ပေးပါ။ အင်တာနက်သုံး၍ အပ်ဒိတ်လုပ်ခဲ့သော ရက်စွဲအရ လေးထောင့်ကွက်ဆိုဒ် အပိုင်းအသစ်များထွက်ရှိထားပါသည် @@ -187,8 +183,6 @@ စတင်ခြင်းလမ်းညွှန် မမှန်ကန်သော အပိုင်းပုံစံဖြစ်နေသည် Filter လုပ်နေစဥ်တွင် ဗလာဖြစ်နေသည့်အမျိုးအစားများပါ ပြပေးပါ - အပိုင်းများဒေါင်းလုဒ်ဆွဲဖို့အတွက် TachiyomiJ2K အား ဖိုင်အားလုံးရယူခွင့်ပေးဖို့ လိုအပ်ပါသည်။ ဤနေရာကိုနှိပ်ပြီး \"ဖိုင်အားလုံးစီမံပိုင်ခွင့်\" ကို ဖွင့်ပေးပါ။ - RARv5 format ကို အထောက်အပံ့မပေးထားပါ သင့် filter နှင့် ကိုက်ညီသောရလဒ်မတွေ့ရှိပါ Beta ဗားရှင်းအသစ် ထွက်နေပါပြီ! အပ်ဒိတ်ကို သွင်းလို့မရခဲ့ပါ @@ -219,4 +213,4 @@ သက်တောင့်သက်သာရှိသော လေးထောင့်ပုံစံ ကျစ်ကျစ်လစ်လစ်ရှိသော လေးထောင့်ပုံစံ အပ်ဒိတ်အများအပြားလုပ်ခြင်းအားဖြင့် ဘက်ထရီသုံးစွဲမှု ပိုမိုများပြားစေသည့်အပြင် ရင်းမြစ်များကြည့်ရှုရာတွင်လည်း ပိုမိုနှေးကွေးစေနိုင်ပါသည်။ ပိုမိုသိရှိရန် ဤနေရာကိုနှိပ်ပါ။ - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/nb-rNO/plurals.xml b/i18n/src/commonMain/moko-resources/nb-rNO/plurals.xml index 3ff93df564..8672920ff1 100644 --- a/i18n/src/commonMain/moko-resources/nb-rNO/plurals.xml +++ b/i18n/src/commonMain/moko-resources/nb-rNO/plurals.xml @@ -1,46 +1,33 @@ - Utvidelsesoppdatering tilgjengelig %d utvidelsesoppdateringer tilgjengelige - Gjort på %1$s med %2$s feil Gjort på %1$s med %2$s feil - %d kategori %d kateogrier - Fjern %1$d nedlastet kapittel\? Fjern %1$d nedlastede kapitler\? - Hopper over %d kapittel, enten så mangler kilden den eller så har den blitt filtrert ut Hopper over %d kapitler, enten så mangler kilden de eller så har de blitt filtrert ut - For én tittel For %d titler - - - %1$d side igjen - %1$d sider igjen - - %1$s kapittel %1$s kapitler - kapittel har blitt fjernet fra denne kilden \n%2$s @@ -51,79 +38,64 @@ \n \nSlett nedlastningenene av dem\? - Opprenskning ferdig. Fjernet én mappe Opprenskning ferdig. Fjernet %d mapper - og %1$d kapittel til og %1$d kapitler til - Hurtiglager tømt. %d fil har blitt slettet. Hurtiglager tømt. %d filer har blitt slettet. - Etter %1$s minutt Etter %1$s minutter - %d manga flyttet %d manga flyttet - Kopier %1$d%2$s manga\? Kopier %1$d%2$s manga\? - Flytt %1$d%2$s manga\? Flytt %1$d%2$s manga\? - %1$d side %1$d sider - Utvidelse oppdatert %d utvidelser oppdatert - %d oppdatering venter %d oppdateringer venter - Neste uleste kapittel Neste %d uleste kapitler - %d språk %d språk - %d serietype %d serietyper - %d status %d statuser - %d kilde %d kilder - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml b/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml index 845c370d97..38b53fb2ba 100644 --- a/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml +++ b/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml @@ -445,7 +445,6 @@ Legg til %1$s i … Underveis Ikke startet - Filtilganger kreves Navn Ulest Avskrudd @@ -717,10 +716,6 @@ Åpne en tilfeldig serie Manhua Manhwa - TachiyomiJ2K krever tilgang til alle filer for å laste ned kapitler. Trykk her og skru på «Tillat tilgang til håndtering av alle filer.» - TachiyomiJ2K krever tilgang til alle filer i Android 11 for å laste ned kapitler, opprette automatiske sikkerhetskopier, og lese lokal manga. -\n -\nPå neste skjerm skrur du på «Innvilg tilgang til håndtering av alle filer.» Safirskumring Slett under global oppdatering, spør på kapittelsiden Fjern foreldreløse @@ -871,7 +866,6 @@ Last ned automatisk mens du leser Kunne ikke dele det nedlastede bildet Side %d ble ikke funnet under deling - RARv5-formatet støttes ikke Last ned i forkant Fungerer kun på oppføringer i biblioteket, og hvis det nåværende kapittelet samt det neste allerede er lastet ned Del opp høye bilder @@ -961,4 +955,4 @@ Programinnstillinger Debug info Bakgrunnsaktivitet - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ne/plurals.xml b/i18n/src/commonMain/moko-resources/ne/plurals.xml index a4e8ee3263..458ba1222d 100644 --- a/i18n/src/commonMain/moko-resources/ne/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ne/plurals.xml @@ -1,91 +1,69 @@ - %d वर्ग %d वर्गहरू - %1$s अध्याय %1$s अध्यायहरू - एक्सटेन्शन अपडेट उपलब्ध छ %d एक्सटेन्शन अपडेटहरू उपलब्ध छन् - अर्को नपढिएको अध्याय अर्को %d नपढिएका अध्यायहरू - %2$s त्रुटिको साथ %1$s मा सम्पन्न भयो %2$s त्रुटिहरूसँग %1$s मा सम्पन्न भयो - %1$s मिनेट पछि %1$s मिनेट पछि - %d अध्याय छोड्दै, या त स्रोत सँग छैन वा यसलाई फिल्टर गरिएको छ %d अध्यायहरू छोड्दै, या त स्रोत सँग छैन वा यसलाई फिल्टर गरिएको छ - श्रृङ्खला प्रकार %d श्रृङ्खला प्रकारहरू - स्रोत %d स्रोतहरू - भाषा %d भाषाहरू - - - %1$d पृष्ठ बाँकी छ - %1$d पृष्ठहरू बाँकी छन् - - %1$d डाउनलोड गरिएको अध्याय हटाउनुहुन्छ\? %1$d डाउनलोड गरिएका अध्यायहरू हटाउनुहुन्छ\? - %d अपडेट विचाराधीन %d अपडेटहरू विचाराधीन छन् - %d शीर्षकको लागि %d शीर्षकहरूको लागि - र थप %1$d अध्याय र थप %1$d अध्यायहरू - एक्सटेन्शन अपडेट गरियो %d एक्सटेन्शनहरू अपडेट गरियो - %1$d पृष्ठ %1$d पृष्ठहरू - स्रोतबाट एउटा अध्याय हटाइयो: \n%2$s @@ -95,34 +73,28 @@ \n \nतिनीहरूका डाउनलोडहरू हटाउने हो\? - %d श्रृङ्खला स्थानान्तरण भयो %d श्रृङ्खला स्थानान्तरण भयो - %1$d%2$s श्रृङ्खला प्रतिलिपि गर्ने हो\? %1$d%2$s श्रृङ्खला प्रतिलिपि गर्ने हो\? - %1$d%2$s श्रृङ्खला स्थानान्तरण गर्ने हो\? %1$d%2$s श्रृङ्खला स्थानान्तरण गर्ने हो\? - सफाई सक्यो। %d फोल्डर हटाइयो सफाई सक्यो। %d फोल्डरहरू हटाइयो - स्थिति %d स्थितिहरू - क्यास खाली गरियो। %d फाइल मेटाइएको छ क्यास खाली गरियो। %d फाइलहरू मेटिएका छन् - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ne/strings.xml b/i18n/src/commonMain/moko-resources/ne/strings.xml index 1481cd0dd2..99ef617e9c 100644 --- a/i18n/src/commonMain/moko-resources/ne/strings.xml +++ b/i18n/src/commonMain/moko-resources/ne/strings.xml @@ -75,7 +75,6 @@ अवैध अध्याय ढाँचा द्वारा अर्डर गर्नुहोस् अध्यायहरू भेटिएन - RARv5 समर्थित छैन अध्याय संख्या द्वारा अपलोड मिति द्वारा वर्गहरू @@ -420,8 +419,6 @@ अज्ञात त्रुटि अनपिन अध्यायहरू हेर्नुहोस् - TachiyomiJ2K लाई अध्यायहरू डाउनलोड गर्न सबै फाइलहरू माथि पहुँच चाहिन्छ। यहाँ ट्याप गर्नुहोस्, त्यसपछि \"Allow access to manage all files.\" सक्षम गर्नुहोस्। - फाइल अनुमति आवश्यक छ माङ्गा मानह्वा मानहुवा @@ -448,9 +445,6 @@ अनलक गर्न आवश्यक छ सबै डाउनलोडहरू हटाउने हो\? ब्याकअप पहिले नै प्रगतिमा छ - TachiyomiJ2K लाई अध्यायहरू डाउनलोड गर्न, स्वचालित ब्याकअपहरू सिर्जना गर्न र लोकल श्रृङ्खला पढ्नको लागि Android 11 मा सबै फाइलहरू माथि पहुँच चाहिन्छ। -\n -\nअर्को स्क्रिनमा, \"Allow access to manage all files.\" सक्षम गर्नुहोस्। ट्र्याकर पुस्तकालयमा शीर्षकहरू ट्र्याक गरिएका शीर्षकहरू @@ -962,4 +956,4 @@ एप सेटिङहरू Debug जानकारी ब्याकग्राउण्ड गतिविधि - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/nl/plurals.xml b/i18n/src/commonMain/moko-resources/nl/plurals.xml index fca879e264..712674dd35 100644 --- a/i18n/src/commonMain/moko-resources/nl/plurals.xml +++ b/i18n/src/commonMain/moko-resources/nl/plurals.xml @@ -1,66 +1,49 @@ - Extensie-update beschikbaar %d extensie-updates beschikbaar - Na %1$s minuut Na %1$s minuten - en %1$d nog hoofdstuk en %1$d nog hoofdstukken - Voor %d titel Voor %d titels - %d categorie %d categorieën - - - %1$d pagina over - %1$d paginas over - - Verwijder %1$d gedownload hoofdstuk\? Verwijder %1$d gedownloade hoofdstukken\? - Schoonmaak klaar. %d map verwijderd Schoonmaak klaar. %d mappen verwijderd - Cache geleegd. %d bestand is verwijderd Cache geleegd. %d bestanden zijn verwijderd - %d manga gemigreerd %d manga gemigreerd - %1$d%2$s manga kopiëren\? %1$d%2$s manga kopiëren\? - %1$d%2$s manga migreren\? %1$d%2$s manga migreren\? - Een hoofdstuk is verwijderd uit de bron: \n%2$s @@ -70,59 +53,48 @@ \n \nDownloads van deze hoofdstukken verwijderen\? - Voltooid in %1$s met %2$s foutmelding Voltooid in %1$s met %2$s foutmeldingen - %1$s hoofdstuk %1$s hoofdstukken - %1$d pagina %1$d pagina\'s - %d wachtende update %d wachtende updates - Extensie bijgewerkt %d extensies bijgewerkt - 1 hoofdstuk is overgeslagen, de bron mist 1 hoofdstuk of het is uitgefilterd %d hoofdstukken zijn overgeslagen, de bron mist ze of ze zijn uitgefilterd - Volgende ongelezen hoofdstuk Volgende aantal %d ongelezen hoofdstukken - %d serie types - %d bronnen - %d statussen - %d talen - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/nl/strings.xml b/i18n/src/commonMain/moko-resources/nl/strings.xml index 12acadcaff..759326871a 100644 --- a/i18n/src/commonMain/moko-resources/nl/strings.xml +++ b/i18n/src/commonMain/moko-resources/nl/strings.xml @@ -706,9 +706,6 @@ Markeer een reeks hoofstukken als gelezen Alles annuleren voor deze serie App-snelkoppelingen - TachiyomiJ2K heeft toegang nodig tot alle bestanden in Android 11 om hoofdstukken te downloaden, automatische backups aan te maken, en lokale manga te lezen. -\n -\nOp het volgende scherm, schakel \"Toegang verlenen om alle bestanden te beheren\" in. Oriëntatie Standaardoriëntatie Inbegrepen: %s @@ -742,8 +739,6 @@ Op hoofdstuknummer Op volgorde van de bron Geen bladwijzer - TachiyomiJ2K heeft toegang nodig tot alle bestanden om hoofdstukken te downloaden. Tik hier, en zet dan \"Toegang verlenen om alle bestanden te beheren.\" aan. - Bestandsrechten vereist Backup/herstellen werkt waarschijnlijk niet goed als de MIUI Optimalisatie is uitgeschakeld. Alleen via Wi-Fi Tako @@ -883,7 +878,6 @@ Lavender Voor sommige talen moet de app opnieuw worden gestart om correct weer te geven Download vooruit - RARv5-indeling wordt niet ondersteund Tik op zones Violet Standaard user agent string @@ -958,4 +952,4 @@ Bibliotheek Foutopsporingsinformatie Achtergrond activiteit - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/om/plurals.xml b/i18n/src/commonMain/moko-resources/om/plurals.xml index 98ccfd6a22..49b7188649 100644 --- a/i18n/src/commonMain/moko-resources/om/plurals.xml +++ b/i18n/src/commonMain/moko-resources/om/plurals.xml @@ -1,23 +1,15 @@ - Boqonnaa %1$d buufame haquu\? Boqonnaawwan %1$d buufaman haquu\? - Boqonnaa %1$s Boqonnaawwan %1$s - - - fuula %1$d hafe - fuulootaa %1$d hafe - - Ramaddi %d Ramaddiiwwan %d - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/om/strings.xml b/i18n/src/commonMain/moko-resources/om/strings.xml index 1c04eca211..1a19b8540b 100644 --- a/i18n/src/commonMain/moko-resources/om/strings.xml +++ b/i18n/src/commonMain/moko-resources/om/strings.xml @@ -1,6 +1,5 @@ - Hayyama faayilii barbaachisa Mangaa Hin jalqabne Adeemsa irra jira @@ -37,11 +36,7 @@ Guyyaa olkaa\'ameen Ramaddii Ramaddiiwwan - TachiyomiJ2K boqonnaawwan buusuuf, ofumaan dilbeessa uumuu fi mangaa naannoo dubbisuuf, faayiloota Android 11 keessa jiran hunda argachuu barbaachisa. -\n -\n Foddaa itti aanu irratti, \"Allow access to manage all files.\" Kan jedhuuf eeyyami . Baacoo - TachiyomiJ2K boqonnaawwan buusuuf faayiloota hunda argachuu barbaada. As tuqi, sana booda \"Allow access to manage all files\" kan jedhuuf eeyyami. Barreessaa Boqonnaawwan haaraa Maxxansi xumurame @@ -75,4 +70,4 @@ Dogoggora hin beekamne Ramaddiiwwan gulaali Ramaddii bulchuu - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pl/plurals.xml b/i18n/src/commonMain/moko-resources/pl/plurals.xml index 986a28ebe4..86bc50d7c0 100644 --- a/i18n/src/commonMain/moko-resources/pl/plurals.xml +++ b/i18n/src/commonMain/moko-resources/pl/plurals.xml @@ -1,55 +1,41 @@ - Usunąć %1$d pobrany rozdział? Usunąć %1$d pobrane rozdziały? Usunąć %1$d pobranych rozdziałów? Usunąć %1$d pobranych rozdziałów? - %1$s rozdział %1$s rozdziały %1$s rozdziałów %1$s rozdziałów - - - Pozostała %1$d strona - Pozostały %1$d strony - Pozostało %1$d stron - Pozostało %1$d stron - - %d kategoria %d kategorii %d kategorii %d kategorii - Dla %d tytułu Dla %d tytułów Dla %d tytułów Dla %d tytułów - i %1$d rozdział więcej i %1$d rozdziały więcej i %1$d rozdziałów więcej i %1$d rozdziałów więcej - Aktualizacja rozszerzenia dostępna %d aktualizacje rozszerzeń dostępne %d aktualizacji rozszerzeń dostępnych %d aktualizacji rozszerzeń dostępnych - Rozdział został usunięty ze źródła: \n%2$s @@ -67,116 +53,100 @@ \n \nUsunąć pobrane\? - Migrować %1$d%2$s mangę? Migrować %1$d%2$s mangi? Migrować %1$d%2$s mang? Migrate %1$d%2$s mang? - Skopiować %1$d%2$s mangę? Skopiować %1$d%2$s mangi? Skopiować %1$d%2$s mang? Skopiować %1$d%2$s mang? - %d manga zmigrowana %d mangi zmigrowane %d mang zmigrowanych %d mang zmigrowanych - Pamięć podręczna wyczyszczona. %d plik został usunięty Pamięć podręczna wyczyszczona. %d pliki zostały usunięty Pamięć podręczna wyczyszczona. %d plików zostało usuniętych Pamięć podręczna wyczyszczona. %d plików zostało usuniętych - Czyszczenie skończone. Usunięto %d folder Czyszczenie skończone. Usunięto %d foldery Czyszczenie skończone. Usunięto %d folderów Czyszczenie skończone. Usunięto %d folderów - Po %1$s minucie Po %1$s minutach Po %1$s minutach Po %1$s minutach - Wykonano w %1$s z %2$s błędem Wykonano w %1$s z %2$s błędami Wykonano w %1$s z %2$s błędami Wykonano w %1$s z %2$s błędami - %1$d strona %1$d strony %1$d stron %1$d stron - Rozszerzenie zaktualizowane %d rozszerzenia zaktualizowane %d rozszerzeń zaktualizowanych %d rozszerzeń zaktualizowanych - %d oczekująca aktualizacja %d oczekujące aktualizacje %d oczekujących aktualizacji %d oczekujących aktualizacji - %d brakujący rozdział %d brakujące rozdziały %d brakujących rozdziałów %d brakujących rozdziałów - Następny nieprzeczytany rozdział Następne %d nieprzeczytane rozdziały Następne %d nieprzeczytanych rozdziałów Następne %d nieprzeczytanych rozdziałów - %d źródło %d źródła %d źródeł %d źródeł - %d status %d statusy %d statusów %d statusów - %d język %d języki %d języków %d języków - %d typ serii %d typów serii %d typów serii %d typów serii - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pl/strings.xml b/i18n/src/commonMain/moko-resources/pl/strings.xml index 9ec922660a..979a0f189d 100644 --- a/i18n/src/commonMain/moko-resources/pl/strings.xml +++ b/i18n/src/commonMain/moko-resources/pl/strings.xml @@ -716,11 +716,6 @@ Źródło nie jest wspierane Funkcje kopii zapasowej mogą nie działać, jeśli optymalizacja nakładki MIUI jest wyłączona. Ukryj zawartość powiadomienia - TachiyomiJ2K wymaga dostępu do wszystkich plików w celu pobrania rozdziałów. Dotknij tutaj, a następnie włącz \"Zezwól na dostęp do wszystkich plików.\" - TachiyomiJ2K wymaga dostępu do wszystkich plików w Android 11 w celu pobierania rozdziałów, tworzenia automatycznych backupów oraz czytania lokalnie zapisanych mang. -\n -\nNa następnym ekranie włącz opcję \"Zezwól na dostęp do wszystkich plików.\" - Wymagany jest dostęp do plików Podgląd Ostatnio zainstalowane Ostatnio zaktualizowane @@ -929,7 +924,6 @@ Otwórz w aplikacji 5% Domyślny user agent string - Format RARv5 jest nieobsługiwany Poziom baterii nie jest niski Wersje beta mogą być niestabilne i mogą wymagać wyczyszczenia danych aplikacji. Fiołkowy @@ -996,4 +990,4 @@ Biblioteka Informacje debugowania Aktywność w tle - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml b/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml index 27d71c4da8..0ea1b27969 100644 --- a/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml +++ b/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml @@ -1,48 +1,35 @@ - Remover %1$d capítulo baixado\? Remover %1$d capítulos baixados\? Remover %1$d capítulos baixados\? - %1$s capítulo %1$s capítulos %1$s capítulos - - - %1$d página restante - %1$d páginas restantes - %1$d páginas restantes - - %d categoria %d categorias %d categorias - Para %d título Para %d títulos Para %d títulos - e mais %1$d capítulo e mais %1$d capítulos e mais %1$d capítulos - Atualização de extensão disponível %d atualizações de extensão disponíveis %d atualizações de extensão disponíveis - Um capítulo foi removido da fonte: \n%2$s @@ -56,100 +43,84 @@ \n \nExcluir seus downloads\? - Migrar %1$d%2$s mangá\? Migrar %1$d%2$s mangás\? Migrar %1$d%2$s mangás\? - Copiar %1$d%2$s mangá\? Copiar %1$d%2$s mangás\? Copiar %1$d%2$s mangás\? - %d mangá migrado %d mangás migrados %d mangás migrados - Cache limpo. %d arquivo foi excluído Cache limpo. %d arquivos foram excluídos Cache limpo. %d arquivos foram excluídos - Limpeza realizada. %d pasta removida Limpeza realizada. %d pastas removidas Limpeza realizada. %d pastas removidas - Após %1$s minuto Após %1$s minutos Após %1$s minutos - Concluído em %1$s com %2$s erro Concluído em %1$s com %2$s erros Concluído em %1$s com %2$s erros - %1$d página %1$d páginas %1$d páginas - %d atualização pendente %d atualizações pendentes %d atualizações pendentes - Extensão atualizada %d extensões atualizadas %d extensões atualizadas - Pulando %d capítulo, ou ele está faltando na fonte, ou ele foi filtrado Pulando %d capítulos, ou eles estão faltando na fonte, ou eles foram filtrados Pulando %d capítulos, ou eles estão faltando na fonte, ou eles foram filtrados - Próximo capítulo não lido Próximos %d capítulos não lidos Próximos %d capítulos não lidos - %d Tipo de série %d Tipos de séries %d Outras séries - %d Idioma %d Idiomas %d Outros Idiomas - %d Fonte %d Fontes %d Outras Fontes - %d estado %d estados %d outros estados - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml index 178f231dfb..3c903dc12e 100644 --- a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml +++ b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml @@ -1,6 +1,5 @@ - Bem Vindo(a)! Vamos definir algumas coisas primeiro. Você sempre pode fazer alterações nas configurações depois também. Começar @@ -17,11 +16,9 @@ Uso de bateria em plano de fundo Evite interrupções para tarefas longas como atualizações da biblioteca, downloads e restauração de backups. Conceder - Local de armazenamento não definido Local inválido: %s Local inválido - Mangás @@ -366,13 +363,11 @@ Procurar por atualizações Tela segura Segurança - Dados e armazenamento Local de armazenamento Uso de armazenamento Disponível: %1$s / Total: %2$s - Backup Criar backup @@ -428,7 +423,6 @@ Usar as últimas preferências de pré-migração salvas e fontes para migrar em massa Você também pode migrar selecionando o mangá na sua biblioteca - Repositórios de extensões Adicionar novo Repositório @@ -441,7 +435,6 @@ Substituir Assinatura já existente O repositório %1$s tem a mesma assinatura que %2$s.\nSe isso é esperado, %2$s será substituído, caso contrário entre em contato com o dono do repositório. - Versão Data de compilação @@ -797,9 +790,6 @@ Marcar um intervalo de capítulos como lido Cancelar todos para esta série Atalhos do app - TachiyomiJ2K requer acesso total ao armazenamento do Android 11 para baixar capítulos, criar backups automáticos e ler mangás locais. -\n -\nNa próxima tela ative \"Permitir acesso de gerenciamento de todos arquivos.\" Tema escuro Tema claro Não foi encontrado resultado similar @@ -808,8 +798,6 @@ Atualizações globais Abrir uma série aleatória Mostrar o número de itens - TachiyomiJ2K requer o acesso a todos os arquivos para baixar os capítulos. Toque aqui, então habilite \"Permitir o acesso para gerenciar todos os arquivos\" - Permissão para acessar arquivos Backup/restauração pode não funcionar adequadamente se a Otimização MIUI estiver desabilitada. Definir como padrão Adicionar tag @@ -996,7 +984,6 @@ Violeta 5% User Agent padrão - O formato RARv5 não é suportado Download automático durante a leitura Capítulos disponíveis offline Status @@ -1054,7 +1041,7 @@ Aplicar Informações de depuração Atividade em segundo plano - Revogar todas as extensões confiáveis + Revogar todas as extensões confiáveis Incluir configurações sensíveis (por exemplo, tokens de login de rastreadores) Falha ao adquirir acesso persistente à pasta. O aplicativo pode se comportar de forma inesperada. Erro interno: %s @@ -1079,4 +1066,4 @@ Cortar bordas (tira longa) Auto-anexar ID Abrir repositório de origem - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pt/plurals.xml b/i18n/src/commonMain/moko-resources/pt/plurals.xml index 0f14a524e8..a8e0304e51 100644 --- a/i18n/src/commonMain/moko-resources/pt/plurals.xml +++ b/i18n/src/commonMain/moko-resources/pt/plurals.xml @@ -1,89 +1,69 @@ - Atualização de extensão disponível %d atualizações de extensão disponíveis %d atualizações de extensão disponíveis - %d categoria %d categorias %d categorias - - - %1$d página restante - %1$d páginas restantes - %1$d páginas restantes - - Remover %1$d capítulo transferido\? Remover %1$d capítulos transferidos\? Remover %1$d capítulos transferidos\? - Concluído em %1$s com %2$s erro Concluído em %1$s com %2$s erros Concluído em %1$s com %2$s erros - %1$s capítulo %1$s capítulos - e mais %1$d capítulo e mais %1$d capítulos e mais %1$d capítulos - Para %d título Para %d títulos Para %d títulos - Após %1$s minuto Após %1$s minutos Após %1$s minutos - Limpeza realizada. %d pasta removida Limpeza realizada. %d pastas removidas Limpeza realizada. %d pastas removidas - Cache limpa. %d ficheiro foi apagado Cache limpa. %d ficheiros foram apagados Cache limpa. %d ficheiros foram apagados - %d manga migrada %d mangas migradas %d mangas migradas - Copiar %1$d%2$s manga\? Copiar %1$d%2$s mangas\? Copiar %1$d%2$s mangas\? - Migrar %1$d%2$s manga\? Migrar %1$d%2$s mangas\? Migrar %1$d%2$s mangas\? - Um capítulo foi removido da fonte: \n%2$s @@ -95,58 +75,49 @@ \n%2$s \nApagar as suas transferências\? - %1$d página %1$d páginas %1$d páginas - Extensão atualizada %d extensões atualizadas %d extensões atualizadas - %d atualização pendente %d atualizações pendentes %d atualizações pendentes - Ignorando %d capítulo, ou a fonte está em falta ou foi filtrado Ignorando %d capítulos, ou a fonte está em falta ou foi filtrado Capítulos %d ignorados, ou a fonte está em falta ou foi filtrado - 1 tipo de série %d tipos de séries %d tipos de séries - 1 fonte %d fontes %d fontes - 1 estado %d estados %d estados - 1 idioma %d idiomas %d idiomas - Próximo capítulo não lido Próximos %d capítulos não lidos Próximos %d capítulos não lidos - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/pt/strings.xml b/i18n/src/commonMain/moko-resources/pt/strings.xml index 2e5ab32a5c..bf86fef1a1 100644 --- a/i18n/src/commonMain/moko-resources/pt/strings.xml +++ b/i18n/src/commonMain/moko-resources/pt/strings.xml @@ -787,11 +787,6 @@ Por número do capítulo Pela ordem da fonte Não foi marcado - TachiyomiJ2K requer o acesso total aos ficheiros para descarregar os capítulos. Clique aqui, então ative \"Permitir o acesso para gerir todos os ficheiros\" - TachiyomiJ2K requer acesso a todos os ficheiros no Android 11 para transferir capítulos, criar backups automáticos, e ler mangas locais. -\n -\nNo próximo ecrã, ative \"Permitir acesso de gestão de todos os ficheiros.\" - Permissões de ficheiro necessárias Isto forçará a cache transferida a recalcular. Útil se modificou transferências fora desta app e quer que a app as apanhe Usar navegação lateral Safira sombreado @@ -916,7 +911,6 @@ Melhora o desempenho do leitor Zonas de toque 5% - O formato RARv5 não é suportado Capítulos transferidos Transferência automática durante leitura Apenas funciona em entradas na biblioteca e se o capítulo atual, mais o próximo, já estiverem transferidos @@ -988,4 +982,4 @@ Permitir as notificações é recomendado para manter o aplicativo e sua biblioteca atualizados. Informações de depuração Atividade em segundo plano - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ro/plurals.xml b/i18n/src/commonMain/moko-resources/ro/plurals.xml index 5811d9d04f..4a39b90b64 100644 --- a/i18n/src/commonMain/moko-resources/ro/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ro/plurals.xml @@ -1,66 +1,50 @@ - Actualizare de extensie disponibilă %d actualizări de extensie disponibile %d actualizări de extensie disponibile - Gata în %1$s cu eroarea %2$s Gata în %1$s cu %2$s erori Gata în %1$s cu %2$s erori - %d categorie %d categorii %d categorii - - - %1$d pagină rămasă - %1$d pagini rămase - %1$d pagini rămase - - %1$s capitol %1$s capitole %1$s capitole - Eliminați %1$d capitolul descărcat\? Eliminați %1$d capitole descărcate\? Eliminați %1$d capitole descărcate\? - și %1$d capitol mai mult și %1$d mai multe capitole și %1$d mai multe capitole - Pentru %d titlu Pentru %d titluri Pentru %d titluri - Copiați %1$d%2$s manga\? Copiați %1$d%2$s manga\? Copiați %1$d%2$s manga\? - Migrează %1$d%2$s manga\? Migrează %1$d%2$s manga\? Migrează %1$d%2$s manga\? - Un capitol a fost eliminat din sursă: \n%2$s @@ -74,82 +58,69 @@ \n \nȘtergeți descărcarea acestora\? - %1$d pagină %1$d pagini %1$d pagini - Curățenie făcută. A fost eliminat %d dosar Curățenie făcută. Au fost eliminate %d dosare Curățenie făcută. Au fost eliminate %d dosare - %d manga a migrat %d mangauri au migrat %d mangauri au migrat - După %1$s minut După %1$s minute După %1$s minute - Cache curățat. %d fișierul a fost șters Cache curățat. %d fișiere au fost șterse Cache curățat. %d fișiere au fost șterse - Următorul capitol necitit Următoarele %d capitole necitite Următoarele %d capitole necitite - %d actualizare în așteptare %d actualizări în așteptare %d actualizări în așteptare - Extensia a fost actualizată Extensiile %d au fost actualizate Extensiile %d au fost actualizate - Omiterea %d capitol, fie că sursa lipsește, fie că a fost filtrată Omiterea a %d capitole, fie că sursa nu le are, fie că au fost filtrate Omiterea a %d capitole, fie că sursa nu le are, fie că au fost filtrate - %d Tip de serii %d Tipuri de serii %d Tipuri de serii - %d sursă %d surse %d surse - %d Limbă %d Limbi %d Limbi - %d status %d statusuri %d statusuri - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ro/strings.xml b/i18n/src/commonMain/moko-resources/ro/strings.xml index 1a5bf48696..5e25f5fa05 100644 --- a/i18n/src/commonMain/moko-resources/ro/strings.xml +++ b/i18n/src/commonMain/moko-resources/ro/strings.xml @@ -637,11 +637,6 @@ Aspect Sincronizare unidirecțională pentru actualizarea progresului capitolului în cadrul serviciilor de urmărire. Configurați urmărirea pentru intrările manga individuale din butonul de urmărire al acestora. Niciun capitol de șters - TachiyomiJ2K are nevoie de acces la toate fișierele pentru a descărca capitole. Atingeți aici, apoi activați \"Permiteți accesul pentru a gestiona toate fișierele\". - Permisiuni necesare pentru a accesa fișierele - TachiyomiJ2K necesită acces la toate fișierele din Android 11 pentru a descărca capitole, pentru a crea copii de rezervă automate și pentru a citi manga locală. -\n -\nÎn ecranul următor, activați \"Allow access to manage all files\" (Permiteți accesul pentru a gestiona toate fișierele). Înlăturați toate descărcările\? Editare finalizată Anulat @@ -902,7 +897,6 @@ Selectați sursele dezinstalate Link-uri utile de traducere Nu s-a putut diviza imaginea descărcată - Formatul RARv5 nu este acceptat 5% Unele limbi pot necesita o relansare a aplicației pentru a fi afișate corect Păstrează manga cu capitole citite @@ -953,4 +947,4 @@ Program instalare Intrări în bibliotecă Activități în fundal - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ru/plurals.xml b/i18n/src/commonMain/moko-resources/ru/plurals.xml index 175ad0788a..5b5e27100e 100644 --- a/i18n/src/commonMain/moko-resources/ru/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ru/plurals.xml @@ -1,90 +1,71 @@ - Кэш очищен. %d файл был удален Кэш очищен. %d файла было удалено Кэш очищен. %d файлов было удалено Кэш очищен. %d файлов было удалено - Очистка завершена. %d папка удалена Очистка завершена. %d папки удалено Очистка завершена. %d папок удалено Очистка завершена. %d папок удалено - Копировать %1$d%2$s серию\? Копировать %1$d%2$s серии\? Копировать %1$d%2$s серий\? Копировать %1$d%2$s серий\? - Мигрировать %1$d%2$s серию\? Мигрировать %1$d%2$s серии\? Мигрировать %1$d%2$s серий\? Мигрировать %1$d%2$s серий\? - Через %1$s минуту Через %1$s минуты Через %1$s минут Через %1$s минут - %d серия перемещёна %d серии перемещено %d серий перемещено %d серий перемещено - и ещё %1$d глава и ещё %1$d главы и ещё %1$d глав и ещё %1$d глав - Для %d серии Для %d серий Для %d серий Для %d серий - Удалить %1$d загруженную главу\? Удалить %1$d загруженных глав\? Удалить %1$d загруженных глав\? Удалить %1$d загруженных глав\? - %1$s глава %1$s главы %1$s глав %1$s глав - - - %1$d страница осталась - %1$d страницы осталось - %1$d страниц осталось - %1$d страниц осталось - - %d категория %d категории %d категорий %d категорий - %1$s глава была удалена из источника: \n%2$s @@ -99,81 +80,70 @@ \n%2$s \nУдалить загрузку\? - Доступно %d обновление для расширения Доступны %d обновления для расширений Доступны %d обновлений для расширений Доступны %d обновлений для расширений - Выполнено за %1$s с %2$s ошибкой Выполнено за %1$s с %2$s ошибками Выполнено за %1$s с %2$s ошибками Выполнено за %1$s с %2$s ошибками - %1$d страница %1$d страницы %1$d страниц %1$d страниц - %d обновление ожидается %d обновления ожидаются %d обновлений ожидаются %d обновлений ожидаются - Обновлено %d расширение Обновлено %d расширения Обновлено %d расширений Обновлено %d расширений - Пропущена %d глава, так как она либо отсутствует в источнике, либо была отфильтрована Пропущено %d главы, так как они либо отсутствуют в источнике, либо были отфильтрованы Пропущено %d глав, так как они либо отсутствуют в источнике, либо были отфильтрованы Пропущено %d глав, так как они либо отсутствуют в источнике, либо были отфильтрованы - Следующая непрочитанная глава Следующие %d непрочитанные главы Следующие %d непрочитанных глав Следующие %d непрочитанных глав - %d тип серии %d типа серии %d типов серий %d типов серий - %d серия %d серии %d серий %d серий - %d статус %d статуса %d статусов %d статусов - %d язык %d языка %d языков %d языков - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ru/strings.xml b/i18n/src/commonMain/moko-resources/ru/strings.xml index 287787a519..31efbdc048 100644 --- a/i18n/src/commonMain/moko-resources/ru/strings.xml +++ b/i18n/src/commonMain/moko-resources/ru/strings.xml @@ -736,9 +736,6 @@ Отметить определённое количество глав, как «Прочитано» Отменить всё для этой серии Ярлыки приложения - TachiyomiJ2K требует доступ ко всем файлам системы Android 11 для загрузки глав, создания автоматических резервных копий и чтения личных серий. -\n -\nНа следующем экране включите «Разрешить доступ к управлению всеми файлами.» Включать: %s Исключать: %s Это заставит пересчитать кэш загрузок. Полезно, если вы изменили загрузки вне этого приложения и хотите, чтобы приложение их подхватило @@ -762,8 +759,6 @@ Глобальные обновления Открыть случайную серию Показать количество серий - TachiyomiJ2K требует доступ ко всем файлам для загрузки глав. Нажмите здесь, затем включите «Разрешить доступ к управлению всеми файлами.» - Требуются права доступа к файлам Ориентация Ориентация по умолчанию Резервная копия/Восстановление может не работать должным образом, если отключена «Оптимизация MIUI». @@ -935,7 +930,6 @@ 5% User agent по умолчанию Некоторым языкам требуется перезапуск приложения для корректного отображения - Формат RARv5 не поддерживается При чтении Загруженные главы Статистика @@ -991,4 +985,4 @@ Настройки приложения Отладочная информация Фоновая активность - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sc/plurals.xml b/i18n/src/commonMain/moko-resources/sc/plurals.xml index e120eef11c..56eaabefb0 100644 --- a/i18n/src/commonMain/moko-resources/sc/plurals.xml +++ b/i18n/src/commonMain/moko-resources/sc/plurals.xml @@ -1,41 +1,33 @@ - B\'at un\'agiornamentu a disponimentu pro un\'estensione B\'ant agiornamentos a disponimentu pro %d estensiones - A pustis de %1$s minutu A pustis de %1$s minutos - Innetadura acabada. %d cartella iscantzellada Innetadura acabada. %d cartellas iscantzelladas - Memòria temporànea isboidada. %d documentu est istadu iscantzelladu Memòria temporànea isboidada. %d documentos sunt istados iscantzellados - %d manga tramudadu %d manga tramudados - Copiare %1$d%2$s manga\? Copiare %1$d%2$s manga\? - Tramudare %1$d%2$s manga\? Tramudare %1$d%2$s manga\? - Unu capìtulu est istadu bogadu dae sa mitza: \n%2$s @@ -45,84 +37,64 @@ \n \nCheres iscantzellare sos iscarrigamentos issoro\? - e %1$d àteru capìtulu e àteros %1$d capìtulos - Pro %d tìtulu Pro %d tìtulos - %d categoria %d categorias - - - Galu %1$d pàgina - Galu %1$d pàginas - - Iscantzellare %1$d capìtulu iscarrigadu\? Iscantzellare %1$d capìtulos iscarrigados\? - Fatu in %1$s cun %2$s errore Fatu in %1$s cun %2$s errores - %1$s capìtulu %1$s capìtulos - %1$d pàgina %1$d pàginas - %d agiornamentu in isetu %d agiornamentos in isetu - Estensione agiornada %d estensiones agiornadas - Brinchende %d capìtulu, sa fonte non lu tenet o est istadu bogadu cun unu filtru Brinchende %d capìtulos, sa fonte non los tenet o sunt istados bogados cun unu filtru - Su capìtulu non lèghidu imbeniente Sos %d capìtulos non lèghidos imbenientes - %d fonte %d fontes - %d istadu %d istados - %d limba %d limbas - %d casta de sèrie %d castas de sèrie - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sc/strings.xml b/i18n/src/commonMain/moko-resources/sc/strings.xml index c4ec13b5c3..00541b2482 100644 --- a/i18n/src/commonMain/moko-resources/sc/strings.xml +++ b/i18n/src/commonMain/moko-resources/sc/strings.xml @@ -729,11 +729,6 @@ Agiornamentos globales Aberi una sèrie a casu Ammustra su nùmeru de elementos - Pro iscarrigare capìtulos TachiyomiJ2K tenet bisòngiu de s\'atzessu a totu sos documentos. Incarca inoghe, e a pustis abìlita \"Permite s\'atzessu pro amministrare totu sos documentos.\" - Pro iscarrigare capìtulos, creare còpias de seguresa e lèghere manga in locale TachiyomiJ2K tenet bisòngiu de s\'atzessu a sos documentos de Android 11. -\n -\nIn s\'ischermada imbeniente abìlita \"Permite s\'atzessu pro amministrare totu sos documentos.\" - Permissos de iscritura pedidos Orientamentu Orientamentu predefinidu Sa còpia de seguresa e su riprìstinu diant pòdere non funtzionare comente si tocat si s\'otimizatzione MIUI est disabilitada. @@ -905,7 +900,6 @@ 5% Istringa de agente de utente predefinida Unas cantas limbas diant pòdere bisongiare chi torres a allùghere s\'aplicatzione pro las ammustrare comente si tocat - Su formadu RARv5 no est suportadu Mantene sos manga cun capìtulos lèghidos Totu sos manga lèghidos Iscàrriga in automàticu durante sa letura @@ -959,4 +953,4 @@ Elementos de sa biblioteca Informatziones de depuratzione de còdighe Atividade in s\'isfundu - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/si/strings.xml b/i18n/src/commonMain/moko-resources/si/strings.xml index 983a9539a9..8e95ce06da 100644 --- a/i18n/src/commonMain/moko-resources/si/strings.xml +++ b/i18n/src/commonMain/moko-resources/si/strings.xml @@ -4,7 +4,6 @@ පරිච්ඡේදය %1$s පරිච්ඡේද %2$d හි %1$d වන පරිච්ඡේදය නම - ෆයිල් සඳහා අවසරය අවශ්‍යයි මන්හ්වා (කොරියන්) මන්හුවා (චීන) කොමික් @@ -15,11 +14,7 @@ නවතා ඇත ප්‍රවේශ වීමට අගුලු හරින්න අන්ලොක් - තචියොමිJ2K සියලුම ෆයිල් සඳහා ප්‍රවේශ වීමට ඉඩ ඉල්ලයි. මෙය ස්වයංක්‍රීය බැකප් සෑදීමටත්, පරිච්ඡේද බාගැනීම් සඳහාත්, දුරකථනයේ ඇති මංගා කියවීම සඳහාත් වේ. -\n -\nඊළග තීරයේ දැක්වෙන \"allow access to manage all files\" සක්‍රීය කරන්න මංගා - තචියොමිJ2K සියලුම ෆයිල් සඳහා ප්‍රවේශ වීමට ඉඩ ඉල්ලයි. මෙය පරිච්ඡේද බාගැනීම් සඳහා වේ. මෙතන ඔබා, \"Allow access to manage all files.\" සක්‍රීය කරන්න කියවීමට පටන් අරන් නෑ චිත්‍ර ශිල්පියා කියවමින් පවතී diff --git a/i18n/src/commonMain/moko-resources/sk/plurals.xml b/i18n/src/commonMain/moko-resources/sk/plurals.xml index b739e82c7d..2397597471 100644 --- a/i18n/src/commonMain/moko-resources/sk/plurals.xml +++ b/i18n/src/commonMain/moko-resources/sk/plurals.xml @@ -1,63 +1,48 @@ - Je dostupná aktualizácia pre rozšírenie Je dostupných %d aktualizácii pre rozšírenia Sú dostupné %d aktualizácii pre rozšírenia - %d kategória %d kategórie %d kategórií - Dokončené za %1$s s %2$s chybou Dokončené za %1$s s %2$s chybami Dokončené za %1$s s %2$s chybami - Ďalšia neprečítaná kapitola Ďalšie %d neprečítané kapitoly Ďalších %d neprečítaných kapitol - %1$s kapitola %1$s kapitoly %1$s kapitol - Odstrániť %1$d stiahnutú kapitolu\? Odstrániť %1$d stiahnute kapitoly\? Odstrániť %1$d stiahnutých kapitol\? - - - Zostáva %1$d strana - Zostávú %1$d strany - Zostáva %1$d stran - - Po %1$s minúte Po %1$s minútach Po %1$s minútach - Pre %d titul Pre %d tituly Pre %d titulov - Preskočenie %d kapitoly, buď zdroj chýba, alebo bol odfiltrovaný Preskočenie %d kapitol, buď zdroj chýba, alebo bol odfiltrovaný Zložky %d kapitoly, buď zdroj chýba, alebo boli filtrované - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sk/strings.xml b/i18n/src/commonMain/moko-resources/sk/strings.xml index cd7e4bcb7b..3186c5b81d 100644 --- a/i18n/src/commonMain/moko-resources/sk/strings.xml +++ b/i18n/src/commonMain/moko-resources/sk/strings.xml @@ -288,7 +288,6 @@ Zrušiť všetko pre túto sériu Zabezpečená obrazovka Obrázok sa nepodarilo načítať - Požadované povolenia súborov Podľa dátumu nahratia Upozornenie Sledované @@ -315,10 +314,6 @@ Vždy Manhwa Komix - TachiyomiJ2K vyžaduje prístup ku všetkým súborom v Android 11 na sťahovanie mangy, vytváranie automatických záloh a čítania stiahnutej mangy. -\n -\nNa nasledujucej obrazovke, povoľte \"Povoliť prístup ku spravovaniu všetkych súborov.\" - TachiyomiJ2K vyžaduje prístup ku všetkým súborom pre stiahnutie časti. Kliknite sem, a potom potvrďte \"Povoliť prístup na spravovanie všetkých súborov\" Vzostupne Zostupne Vyberte inverzne @@ -368,7 +363,6 @@ Optimalizácia batérie je už vypnutá 10% V tvare písmena L - Formát RARv5 nie je podporovaný Žiadne kapitoly na odstránenie Zobrazenie prázdnych kategórií pri filtrovaní Žiadne zhody pre vaše filtre @@ -570,4 +564,4 @@ Inštalátor Záznamy v knižnici Aktivita na pozadí - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sq/strings.xml b/i18n/src/commonMain/moko-resources/sq/strings.xml index ebe0c9c8e3..2afffa51e4 100644 --- a/i18n/src/commonMain/moko-resources/sq/strings.xml +++ b/i18n/src/commonMain/moko-resources/sq/strings.xml @@ -266,7 +266,6 @@ Ruaje si arkiv CBZ Kapitulli i fundit Majtas - Formati RARv5 nuk mbështetet Migroni Më tepër Varg i pavlefshëm i agjentit të përdoruesit @@ -411,4 +410,4 @@ %1$d hyrje jashtë bibliotekës në bazën e të dhënave Fshi historikun për shënimet që nuk janë ruajtur në bibliotekën tënde Aktiviteti në sfond - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sr/plurals.xml b/i18n/src/commonMain/moko-resources/sr/plurals.xml index 3d081648d7..14a8db3c81 100644 --- a/i18n/src/commonMain/moko-resources/sr/plurals.xml +++ b/i18n/src/commonMain/moko-resources/sr/plurals.xml @@ -1,72 +1,55 @@ - Доступна је нова верзија екстензије %d нове верзије екстензије су доступне %d нових верзија екстензија су доступна - %d kategorija %d kategorije %d kategoriji - Прескаче се %d поглавље, или не постоји у извору или је филтером издвојено Прескаче се %d поглавља, или не постоји у извору или је филтером издвојено Прескаче се %d поглавља, или не постоји у извору или је филтером издвојено - Завршено у %1$s са %2$s грешком Завршено у %1$s са %2$s грешке Завршено у %1$s са %2$s грешака - Keš je obrisan. %d datoteka je izbrisana Keš je obrisan. %d datoteke su izbrisane Keš je obrisan. %d datoteke su izbrisane - Kopiraj %1$d%2$s mangu\? Kopiraj %1$d%2$s mange\? Koporaj %1$d%2$s mangi\? - Nakon %1$s minut Nakon %1$s minuta Nakon %1$s minuta - %d ažuriranje je na čekanju %d ažuriranja su na čekanju %d ažuriranje je na čekanju - %1$d strana %1$d strane %1$d Stranice - Čišćenje završeno.Uklonjena %d fascikla Čišćenje završeno.Uklonjene %d fascikle Čišćenje završeno.Uklonjenj %d fascikli - - - %1$d strana ostala - %1$d strana ostalo - %1$d strana ostalo - - Поглавље је уклоњено из извора: \n%2$s @@ -80,76 +63,64 @@ \n \nИзбрисати преузимања\? - %d manga prebačena %d mangi prebačeno %d mangi prebačeno - Premestite %1$d%2$s mangu\? Premestine %1$d%2$s mange\? Premestite %1$d%2$s mangi\? - %1$s poglavlje %1$s poglavlja %1$s poglavlja - Za %d naslov Za %d naslove Za %d naslovi - Još %1$d naslov Još %1$d naslova Jos %1$d naslova - Izbrisati %1$d preuzeto poglavlje\? Izbrisati %1$d preuzeta poglavlja\? Izbrisati %1$d preuzetih poglavlja\? - Екстензија ажурирана %d екстензије су ажуриране %d екстензија је ажурирано - Следеће непрочитано поглавље Следећа %d непрочитана поглавља Следећих %d непрочитаних поглавља - %d врста серије %d врсте серије %d врста серија - %d статус %d статуса %d статуса - %d извор %d извора %d извора - %d језик %d језика %d језика - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sr/strings.xml b/i18n/src/commonMain/moko-resources/sr/strings.xml index f3dfe0d43d..b3baa9fb55 100644 --- a/i18n/src/commonMain/moko-resources/sr/strings.xml +++ b/i18n/src/commonMain/moko-resources/sr/strings.xml @@ -601,7 +601,6 @@ Omogućite samo zakačene izvore za migraciju Dodatak za obaveštenje je ažuriran Upravljate onime što se preuzima - Potrebna je dozvola za datoteku Strip Nepočeto U toku @@ -671,10 +670,6 @@ Oznaceno kao pročitano Nova poglavlja Kategorija - TachiyomiJ2K je potreban pristup svim datotekama u Andorid 11 za preuzimanje poglavlja, pravljenje automatskih rezervin kopija, i čitanje lokalnih mangi. -\n -\nNa sledećem ekranu, dozvolu \"Dozvoli pristum za upravljanje svih datoteka.\" - TachiyomiJ2K traži pristum svim datotekama povodom preuzimanja ppglavlja.Pritisni ovde,a onda dozvoli \"Dozvolite pristum za upravljanje svim datotekama.\" Po redosledu izvora Kategorija sa tim imenom već postoji! Nije moguće instalirati ažuriranje @@ -903,7 +898,6 @@ Прескочи дупликатска поглавља Дозволи брисање забележених поглавља Трекери - RARv5 формат није подржан Није могуће разделити преузету слику Завршена листа Наслови на чекању @@ -958,4 +952,4 @@ Наслови колекције Информације за отклањање грешака Активност у позадини - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sv/plurals.xml b/i18n/src/commonMain/moko-resources/sv/plurals.xml index 8828125957..c8614a951f 100644 --- a/i18n/src/commonMain/moko-resources/sv/plurals.xml +++ b/i18n/src/commonMain/moko-resources/sv/plurals.xml @@ -1,46 +1,33 @@ - Tilläggsuppdatering tillgänglig %d tilläggsuppdateringar tillgängliga - Klar på %1$s med %2$s fel Klar på %1$s med %2$s fel - %d kategori %d kategorier - - - %1$d sida kvar - %1$d sidor kvar - - %1$s kapitel %1$s kapitel - Ta bort %1$d nerladdade kapitel\? Ta bort%1$d nerladdade kapitel\? - och %1$d ytterligare kapitel och %1$d ytterligare kapitel - För %d titel För %d titlar - Ett kapitel har tagits bort från källan: \n%2$s @@ -50,79 +37,64 @@ \n \nTa bort nedladdningen\? - %1$d sida %1$d sidor - Rensning klar. %d mapp har tagits bort Rensning klar. %d mappar har tagits bort - Cache rensad. %d fil har tagits bort Cache rensad. %d filer har tagits bort - %d serie migrerad %d serier migrerade - Kopiera %1$d%2$s serie\? Kopiera %1$d%2$s serier\? - Migrera %1$d%2$s serie\? Migrera %1$d%2$s serier\? - Efter %1$s minut Efter %1$s minuter - %d uppdatering väntar %d uppdateringar väntar - Tillägget uppdaterat %d tillägg uppdaterade - Hoppar över %d kapitel, antingen saknar källan det eller så har det filtrerats bort Hoppar över %d kapitel, antingen saknar källan dem eller så har de filtrerats bort - Nästa olästa kapitel Nästa %d olästa kapitel - %d typ av serie %d typer av serier - %d källa %d källor - %d status %d statusar - %d språk %d språk - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/sv/strings.xml b/i18n/src/commonMain/moko-resources/sv/strings.xml index fd76e23b28..96ff1c5e1e 100644 --- a/i18n/src/commonMain/moko-resources/sv/strings.xml +++ b/i18n/src/commonMain/moko-resources/sv/strings.xml @@ -729,11 +729,6 @@ Globala uppdateringar Öppna en slumpmässig serie Visa antal artiklar - TachiyomiJ2K kräver åtkomst till alla filer för att ladda ner kapitel. Tryck här och aktivera sedan \"Tillåt åtkomst för att hantera alla filer.\" - TachiyomiJ2K kräver tillgång till alla filer i Android 11 för att kunna ladda ner kapitel, skapa automatiska säkerhetskopior, och läsa lokala serier. -\n -\nPå nästa skärm aktiverar du \"Tillåt åtkomst för att hantera alla filer\". - Filbehörigheter krävs Orientering Standardorientering Säkerhetskopiering/återställning kanske inte fungerar korrekt om MIUI-optimering är inaktiverat. @@ -904,7 +899,6 @@ Violett 5% Standardsträng för användaragent - RARv5 formatet stöds inte Nedladdade kapitel Automatisk nedladdning under läsning Statistik @@ -962,4 +956,4 @@ Appinställningar Felsökningsinformation Bakgrundsaktivitet - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/th/plurals.xml b/i18n/src/commonMain/moko-resources/th/plurals.xml index a741c0bd1e..263c7b65a9 100644 --- a/i18n/src/commonMain/moko-resources/th/plurals.xml +++ b/i18n/src/commonMain/moko-resources/th/plurals.xml @@ -1,102 +1,75 @@ - ใช้เวลาไป %1$s โดยมีข้อผิดพลาด %2$s รายการ - %d หมวดหมู่ - มีการอัปเดตส่วนขยาย %d รายการพร้อมใช้งาน - ข้ามตอนที่ %d อาจเป็นเพราะหายมาจากแหล่งที่มาหรือถูกกรองออก - โยกย้าย %1$d%2$s เรื่อง\? - คัดลอก %1$d%2$s เรื่อง\? - ล้างเสร็จสิ้น ลบออกไป %d โฟล์เดอร์ - ต้องการนำ %1$d ตอนที่ดาวน์โหลดไว้ออกหรือไม่\? - %1$s ตอน - %d เรื่องได้โยกย้ายแล้ว - ล้างแคชแล้ว แฟ้ม %d รายการได้ถูกลบ - หลังจาก %1$s นาที - ของเรื่อง %d - และอีก %1$d ตอน - อัปเดต %d รายการที่รอดำเนินการ - อัปเดตแล้ว %d ส่วนขยาย - %1$d หน้า - %1$s ตอนได้ถูกนำออกจากแหล่งที่มาแล้ว: \n%2$s \n \nต้องการลบการดาวน์โหลด\? - - - เหลือ %1$d หน้า - - %d ตอนที่ยังไม่ได้อ่านถัดไป - %d ประเภทเรื่อง - %d สถานะ - %d ภาษา - %d แหล่งที่มา - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/th/strings.xml b/i18n/src/commonMain/moko-resources/th/strings.xml index a9496a499b..32585d1d01 100644 --- a/i18n/src/commonMain/moko-resources/th/strings.xml +++ b/i18n/src/commonMain/moko-resources/th/strings.xml @@ -411,9 +411,6 @@ คู่มือเริ่มต้นใช้งาน Shizuku ไม่ทำงาน กำลังอ่าน - TachiyomiJ2K ต้องการการเข้าถึงไฟล์ทั้งหมดใน Android 11 เพื่อดาวน์โหลดตอน สร้างการสำรองข้อมูลอัตโนมัติ และอ่านซีรีย์ในเครื่อง -\n -\nในหน้าจอถัดไป ให้เปิดใช้งาน \"อนุญาตการเข้าถึงเพื่อจัดการไฟล์ทั้งหมด\" ปลดล็อก หมวดหมู่ แสดงหมวดหมู่ว่างเปล่าเมื่อกรอง @@ -679,8 +676,6 @@ ลบตอนที่นำออกแล้ว ลบตอนที่ดาวน์โหลดไว้หากแหล่งที่มาได้ลบตอนออกในออนไลน์ การลบอัตโนมัติ - จำเป็นต้องมีสิทธิ์ในการเข้าถึงไฟล์ - TachiyomiJ2K ต้องการการเข้าถึงไฟล์ทั้งหมดเพื่อดาวน์โหลดตอน แตะที่นี่ แล้วเปิดใช้งาน \"อนุญาตการเข้าถึงเพื่อจัดการไฟล์ทั้งหมด\" ยังไม่เริ่ม ไม่ระบุสถานะ ปลดล็อกเพื่อเข้าถึงคลัง @@ -905,7 +900,6 @@ 5% ตัวแทนผู้ใช้เริ่มต้น บางภาษาอาจจำเป็นต้องปิดเปิดแอปใหม่ เพื่อให้แสดงได้อย่างถูกต้อง - ไม่รับรองรูปแบบ RARv5 รายการที่อ่านหมดแล้ว ละเว้นรายการที่มีตอนที่อ่านไว้ ดาวน์โหลดอัตโนมัติขณะกำลังอ่าน @@ -962,4 +956,4 @@ การตั้งค่าแอป ข้อมูลดีบัก กิจกรรมเบื้องหลัง - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/tl/strings.xml b/i18n/src/commonMain/moko-resources/tl/strings.xml index 69eab30f00..0c8e2fa5c1 100644 --- a/i18n/src/commonMain/moko-resources/tl/strings.xml +++ b/i18n/src/commonMain/moko-resources/tl/strings.xml @@ -303,6 +303,5 @@ Linisin ang mga nadownload na kabanata Pangkalahatang Paghahanap Pangalan - Kailangan ng pahintulot sa file Tako - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/tr/plurals.xml b/i18n/src/commonMain/moko-resources/tr/plurals.xml index 34e1a5eb9a..cb82341e6a 100644 --- a/i18n/src/commonMain/moko-resources/tr/plurals.xml +++ b/i18n/src/commonMain/moko-resources/tr/plurals.xml @@ -1,76 +1,57 @@ - Uzantı güncellemesi var %d uzantı güncellemesi var - %d kategori %d kategoriler - - - %1$d sayfa kaldı - %1$d sayfa kaldı - - İndirilmiş %1$d bölüm silinsin mi\? İndirilmiş %1$d bölüm silinsin mi\? - %1$s içinde %2$s hatayla tamamlandı %1$s içinde %2$s hatayla tamamlandı - %1$d sayfa %1$d sayfa - ve %1$d bölüm daha ve %1$d bölüm daha - %d başlık için %d başlık için - %1$s bölüm %1$s bölüm - Önbellek temizlendi. %d dosya silindi Önbellek temizlendi. %d dosya silindi - %1$d%2$s manga kopyala\? %1$d%2$s manga kopyala\? - %d manga geçiş yaptı %d manga geçiş yaptı - %1$d%2$s mangayı geçiş yap\? %1$d%2$s mangayı geçiş yap\? - Temizleme bitti. %d klasörü kaldırıldı Temizleme bitti. %d klasörü kaldırıldı - Kaynaktan bir bölüm kaldırıldı: \n%2$s @@ -80,49 +61,40 @@ \n \nİndirmesi silinsin mi\? - %1$s dakika sonra %1$s dakika sonra - 1 güncelleme beklemede %d güncelleme beklemede - Uzantı güncellendi %d uzantı güncellendi - %d bölüm atlanıyor, ya kaynakta yok ya da süzgeçlenmiş %d bölüm atlanıyor, ya kaynakta yok ya da süzgeçlenmiş - Sonraki okunmayan bölüm Sonraki %d okunmayan bölüm - %d kaynak %d kaynak - %d durum %d durum - %d dil %d dil - %d seri türü %d seri türü - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/tr/strings.xml b/i18n/src/commonMain/moko-resources/tr/strings.xml index 8df178a605..169ea722a5 100644 --- a/i18n/src/commonMain/moko-resources/tr/strings.xml +++ b/i18n/src/commonMain/moko-resources/tr/strings.xml @@ -729,11 +729,6 @@ Uzantı yüklenemedi Genel güncellemeler Öge sayısını göster - TachiyomiJ2K\'in, bölümleri indirmek için tüm dosyalara erişmesi gerekir. Buraya dokunun, ardından \"Tüm dosyaları yönetmek için erişime izin ver\"i etkinleştirin - TachiyomiJ2K\'in, bölümleri indirmek, otomatik yedeklemeler oluşturmak ve yerel mangaları okumak için Android 11\'deki tüm dosyalara erişimesi gerekir. -\n -\nBir sonraki ekranda, \"Tüm dosyaları yönetmek için erişime izin ver\" seçeneğini etkinleştirin - Dosya izinleri gerekli Varsayılan yön Yön Yedekleme/geri yükleme, MIUI optimizasyonu devre dışıysa düzgün çalışmayabilir. @@ -905,7 +900,6 @@ %5 Bazı dillerin doğru görüntülenmesi için uygulamanın yeniden başlatılması gerekebilir Öntanımlı kullanıcı aracısı dizgesi - RARv5 biçimi desteklenmiyor Tüm okunan manga Okunan bölümleri olan mangaları tut Durum @@ -962,4 +956,4 @@ Arka plan etkinliği Kaynak ayarları Uygulama ayarları - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/uk/plurals.xml b/i18n/src/commonMain/moko-resources/uk/plurals.xml index d72995b822..85042abb7a 100644 --- a/i18n/src/commonMain/moko-resources/uk/plurals.xml +++ b/i18n/src/commonMain/moko-resources/uk/plurals.xml @@ -1,69 +1,53 @@ - Наявне %d оновлення для розширення %d оновлення для розширень доступні %d оновлень для розширень доступні %d оновлень для розширень доступні - - - %1$d сторінка залишилась - %1$d сторінок залишилось - %1$d сторінок залишилось - %1$d сторінок залишилось - - Видалити %1$d завантажений розділ\? Видалити %1$d завантажених розділів\? Видалити %1$d завантажених розділів\? Видалити %1$d завантажених розділів\? - Для %d заголовку Для %d заголовків Для %d заголовків Для %d заголовків - та %1$d ще розділ та %1$d розділів та %1$d розділів та %1$d розділів - Кеш очищено. %d файл було видалено Кеш очищено. %d файлів було видалено Кеш очищено. %d файлів було видалено Кеш очищено. %d файлів було видалено - %d мангу змігровано %d манґи змігровано %d манґи змігровано %d манґи змігровано - Копіювати %1$d%2$s манґу\? Копіювати %1$d%2$s манґи\? Копіювати %1$d%2$s манґи\? Копіювати %1$d%2$s манґи\? - Мігрувати %1$d%2$s манґу\? Мігрувати %1$d%2$s манґ\? Мігрувати %1$d%2$s манґ\? Мігрувати %1$d%2$s манґ\? - Розділ було видалено з джерела: \n%2$s @@ -81,102 +65,88 @@ \n \nВидалити завантажене\? - Очистку завершено. %d тека видалена Очистку завершено. %d тек видалено Очистку завершено. %d тек видалено Очистку завершено. %d тек видалено - Через %1$s хвилину Через %1$s хвилин Через %1$s хвилин Через %1$s хвилин - Зроблено за %1$s з %2$s помилкою Зроблено за %1$s з %2$s помилками Зроблено за %1$s з %2$s помилками Зроблено за %1$s з %2$s помилками - Пропускається %d розділ, тому що джерело не має його, або він був відфільтрований Пропускаються %d розділи, тому що джерело не має їх, або вони були відфільтровані Пропускання %d розділів, тому що джерело не має їх, або вони були відфільтровані Пропускання %d розділів, тому що джерело не має їх, або вони були відфільтровані - %d категорія %d категорії %d категорій %d категорій - %d Розширення оновлено %d розширення оновлені %d розширеннь оновлені %d розширеннь оновлені - %d оновлення в очікуванні %d оновлення в очікуванні %d оновлень в очікуванні %d оновлень в очікуванні - %1$s розділ %1$s розділи %1$s розділів %1$s розділів - %1$d сторінка %1$d сторінки %1$d сторінок %1$d сторінок - Наступний непрочитаний розділ Наступні %d непрочитані розділи Наступні %d непрочитані розділи Наступні %d непрочитані розділи - %d тип серії %d типи серій %d типів серій %d типів серій - %d серія %d серій %d серій %d серій - %d статус %d статусів %d статусів %d статусів - %d мова %d мов %d мов %d мов - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/uk/strings.xml b/i18n/src/commonMain/moko-resources/uk/strings.xml index 77b4bf2117..15d0549f54 100644 --- a/i18n/src/commonMain/moko-resources/uk/strings.xml +++ b/i18n/src/commonMain/moko-resources/uk/strings.xml @@ -624,12 +624,7 @@ Попередження Попередження: великий об\'єм завантажень може призвести до сповільнення роботи джерел та/або блокуванню Tachiyomi. Що 3 дні - TachiyomiJ2K потребує доступу до всіх файлів в Android 11, щоб завантажувати розділи, створювати автоматичні резервні копії та читати локальну манґу. -\n -\nНа наступному екрані ввімкніть \"Дозволити доступ для керування всіма файлами.\" - TachiyomiJ2K потребує доступу до всіх файлів для завантаження розділів. Натисніть тут, а потім увімкніть \"Дозволити доступ для керування всіма файлами.\" Нова категорія - Потрібен дозвіл доступу до файлів Не в закладинках Підказки щодо пошуку періодично з’являтимуться. Довго натисніть на пропозицію, щоб шукати це. Немає нещодавно прочитаної чи оновленої манґи @@ -904,7 +899,6 @@ Фіолетовий 5% Типовий user agent - Формат RARv5 не підтримується Завантажені розділи Автоматичне завантаження під час читання Статистика @@ -959,4 +953,4 @@ Записи бібліотеки Інформація про налагодження Фонова активність - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/vi/plurals.xml b/i18n/src/commonMain/moko-resources/vi/plurals.xml index 45e45c6456..2145ce46a3 100644 --- a/i18n/src/commonMain/moko-resources/vi/plurals.xml +++ b/i18n/src/commonMain/moko-resources/vi/plurals.xml @@ -1,101 +1,74 @@ - Có %d bản cập nhật của các tiện ích bổ sung - %d danh mục - - - còn %1$d trang - - Xóa %1$d chương đã tải\? - Hoàn tất trong %1$s với %2$s lỗi - Sau %1$s phút - Bộ nhớ đệm tạm đã được làm sạch. Tập tin %d đã được xoá - Làm sạch xong. Đã xoá %d thư mục - Đã di chuyển %d truyện - Sao chép %1$d%2$s \? - Di chuyển %1$d%2$s \? - %1$s chương đã bị xoá khỏi nguồn: \n%2$s \nXoá những gì chúng dã tải\? - %1$d trang - và %1$d chương - Cho %d tiêu đề - %1$s chương - Đã bỏ qua %d chương, vì nguồn đang bị thiếu hoặc đã bị lọc ra - %d tiện ích mở rộng đã được cập nhật - %d bản cập nhật đang chờ - %d chương chưa đọc tiếp - %d loại loạt truyện - %d ngôn ngữ - %d trạng thái - %d nguồn - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/vi/strings.xml b/i18n/src/commonMain/moko-resources/vi/strings.xml index 627acd1068..378a4ba37a 100644 --- a/i18n/src/commonMain/moko-resources/vi/strings.xml +++ b/i18n/src/commonMain/moko-resources/vi/strings.xml @@ -747,11 +747,6 @@ Hiển thị danh mục trống khi dùng bộ lọc Theo số thứ tự chương Chưa được đánh dấu - TachiyomiJ2K cần quyền truy cập tất cả các file để tải chương. Ấn vào đây, rồi xác nhận \"Cho phép quản lý tất cả các file.\" - TachiyomiJ2K cần quyền truy cập vào tất cả các tập tin trên Android 11 để tải xuống các chương, tạo bản sao lưu tự động và đọc truyện đã tải xuống. -\n -\nTrong màn hình tiếp theo, hãy cho phép \"Cho phép truy cập để quản lý tất cả tệp.\" - Yêu cầu quyền truy cập tập tin Hỗ trợ dịch thuật Mở một phần truyện ngẫu nhiên Hiển thị số truyện trong danh mục @@ -937,7 +932,6 @@ Sắp xếp bằng thời gian được nhập về 5% Chuỗi đại diện người dùng mặc định - Định dạng RARv5 không được hỗ trợ Một số ngôn ngữ sẽ cần khởi động lại ứng dụng để hiển thị chính xác Tất cả truyện đã đọc Lưu truyện đang đọc @@ -995,4 +989,4 @@ Cài đặt ứng dụng Thông tin Debug Hoạt động ngầm - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml b/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml index 59646883d2..8b43857a87 100644 --- a/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml +++ b/i18n/src/commonMain/moko-resources/zh-rCN/plurals.xml @@ -1,101 +1,72 @@ - %d 个扩展插件可更新 - %d 个类别 - 还有 %1$d 章 - 共 %d 个漫画 - - - 剩余 %1$d 页 - - 移除已下载的 %1$d 章? - - 第%1$s章已从图源中删除: -\n%2$s -\n是否删除其下载内容? + %1$s个章节在以下图源中已被删除:\n%2$s\n是否删除相应的下载内容? - %d 漫画已迁移 - 复制 %1$d%2$s 漫画? - 迁移 %1$d%2$s 漫画? - 在%1$s分钟之后 - 清除完成,移除了 %d 个目录 - 缓存已清除,%d 的文件已被删除 - 耗时 %1$s,出现 %2$s 个错误 - %1$s 章 - %1$d页 - %d 个待处理更新 - 更新了 %d 个扩展 - 跳过了 %d 章,可能是图源没有这些章节,或者被筛选规则排除了 - 下 %d 个未读章节 - %d 种连载类型 - %d 个图源 - %d 种状态 - %d 种语言 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml index f4b56102dd..639638f565 100644 --- a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml @@ -9,8 +9,8 @@ 书架 书架更新 已选择:%1$d - 扩展 - 扩展信息 + 插件 + 插件信息 筛选 已下载 已添加书签 @@ -88,7 +88,7 @@ 信任 不可信 卸载 - 不可信的插件 + 未被信任的插件 版本: %1$s 语言: %1$s 全屏 @@ -274,7 +274,7 @@ 禁用电池优化 电池优化已被关闭 全局搜索 - 自动检查扩展插件更新 + 自动检查插件更新 官网 开源许可证 电子邮件地址 @@ -508,11 +508,11 @@ 启用缩小 正在阅读 %1$s 设置为全局默认 - 扩展更新 + 插件更新 上次使用 隐藏图源 所有图源 - 搜索拓展… + 搜索插件… 在书架中 重置章节? 搜索近期… @@ -534,7 +534,7 @@ 语言 自动备份 无效的备份文件 - 此插件不是 Tachiyomi 官方插件 + 此插件不是 Tachiyomi 官方插件。 18+ 非官方 可能包含 NSFW (18+) 内容 @@ -558,14 +558,12 @@ 备份不包含任何漫画。 缺少图源: 未登录的追踪源: - 来自备份文件的数据将被还原。 -\n -\n你需要安装所有缺少的扩展,之后登录到跟踪服务以使用它们。 + 来自备份文件的数据将被还原。 \n \n你需要安装所有缺少的插件,之后登录到跟踪服务以使用它们。 已取消还原 %02d 分,%02d 秒 图源迁移指南 NSFW (18+) 图源 - 这并不能防止非官方或可能被错误标记的扩展插件在应用程序中显示 NSFW (18+) 内容。 + 这并不能防止非官方或被错误标记的插件在应用程序中显示 NSFW (18+) 内容。 未找到文件选择应用 下一个 打开日志 @@ -724,29 +722,24 @@ 浅色主题 未找到匹配项 图源不受支持 - 无法安装扩展 + 无法安装插件 全局更新 打开一个随机系列 显示项目数 - TachiyomiJ2K 需要访问所有文件才能下载章节。 点按此处,然后启用“允许访问以管理所有文件” - TachiyomiJ2K 在 Android 11 下需要授权访问所有文件才能进行下载章节、创建自动备份和阅读本地漫画的功能。 -\n -\n在接下来的出现的画面中,请启用“允许访问管理所有文件” - 需要文件权限 方向 默认方向 如果 MIUI 优化被关闭,备份/还原可能无法正常工作。 设为默认 - 要安装扩展必须禁用MIUI优化。 + 必须禁用MIUI优化才能安装插件。 按上传日期 按章节数 按图源顺序 未加为书签 首次打开时忽略刘海屏 - 更平静的你 (动态) - 更明亮的你 (动态) + 更暗淡的Material You (动态) + 更明亮的Material You (动态) 警告 不自动更新 任何网络连接 @@ -754,8 +747,8 @@ 自动更新应用 自动更新 添加标签 - 更新扩展中 - 仍然会提示先安装有些扩展。 + 更新插件中 + 某些插件可能仍然需要先安装。 更新全部 已完成更新 无法安装更新 @@ -765,13 +758,13 @@ 最近安装 最近更新 名称 - 一些扩展可能不会自动更新,如果它们是通过外部途径安装的 - 通知扩展已更新 - 自动更新扩展 + 如果插件是通过外部途径安装的,可能不会被自动更新 + 插件更新后发送通知 + 自动更新插件 拆分双页 - 扩展已更新 + 插件已更新 已安装 %1$s - 待更新扩展 + 插件待更新 影响书架网格封面 限制:%1$s 保留 %1$s 上的两个,仅本地替换 @@ -802,7 +795,7 @@ 扫译小组 错误 大型更新可能会导致电池使用量增加和图源变慢。轻按了解更多。 - 显示在图源和扩展列表中 + 显示在图源和插件列表中 显示封面图轮廓 Shizuku 未在运行 安装并启动 Shizuku 以将 Shizuku 用作插件安装程序。 @@ -894,8 +887,8 @@ 分割长图 可以改善阅读器的性能 阅读中 - 计划读 - 已完结 + 想读 + 读过 搁置中 已放弃 按获取时间排序 @@ -905,7 +898,6 @@ 5% 某些语言可能需要重启应用才能正确显示 默认 User Agent 字符串 - 不支持 RARv5 格式 所有已读漫画 保留有已读章节的漫画 统计详情 @@ -956,10 +948,105 @@ 排除的分类 安装程序 书架中的作品 - 可允许插件在不需要用户确认的情况下安装并对 Android 12 以下的设备启用自动更新 + 允许插件在不需要用户确认的情况下安装,并且在 Android 12 以下的设备上启用自动更新 应用 图源设置 应用设置 调试信息 后台活动 - + 更多设置 + 返回 + 选择文件夹 + 存储指南 + 如果从旧版更新而来、不清楚如何选择,可以查看存储指南中的从 Tachiyomi 迁移部分获得更多信息。 + 欢迎! + 首先调整一些默认设置。您也可以稍后在设置中更改它们。 + 开始使用 + 选择一个文件夹供 %1$s 存放下载的章节、备份文件等。\n\n推荐使用一个专门的文件夹。\n\n已选择文件夹:%2$s + 必要 + 可选(推荐) + 用于发送书架更新等通知。 + 后台运行权限 + 随机打开作品(全局) + 撤销所有已信任的插件? + 条漫 + 在屏幕刘海区域显示内容 + Doki + 获取永久文件存储权限失败,应用程序可能会出现异常。 + 点击这里查看 Cloudflare 帮助 + 本应用需要 WebView 才能运行 + 后退 + 安装应用权限 + 用于在更新时安装应用。 + 通知权限 + 避免需要长时间运行的书架更新、下载、备份恢复任务被中断。 + 允许 + 未设置存储位置 + 无效位置:%s + 无效位置 + 第%1$d页,共%2$d页 + 启用章节左右滑动操作 + 显示下载队列 + 长按“最近” + 长按“浏览” + 默认 + 旧版安装程序尚未实现,目前回退到 PackageInstaller(默认) + 撤销所有已信任的插件 + 打开旧版屏幕刘海设置 + 在 Android 9.0 之前的设备上,您需要在系统设置中手动调整屏幕刘海设置 + 将图片保存到单独的文件夹 + 根据作品标题创建文件夹 + 自定义校色文件 + 默认 (%d) + 存储占用 + 可用:%1$s/总共:%2$s + 包含敏感设置(例如进度记录平台的账号信息) + 上次自动备份时间:%s + 调试模式 + 扫描外部存储上的漫画 + 插件仓库 + 添加新仓库 + 添加仓库 + 仓库网址无效 + 此仓库已存在! + 您尚未添加任何仓库。 + 删除此仓库? + 确定要删除仓库“%s”吗? + 替换 + 签名密钥指纹已存在 + %1$s 仓库的签名密钥指纹与 %2$s 仓库相同。\n如果继续,%2$s 将被替换,否则请联系仓库的维护者。 + 打开图源仓库 + 无法打开网址 + 自动附件ID + 刷新 + SFW + NSFW + 内容类型 + %s 发生了意外错误。建议您截屏此消息,转储崩溃日志,然后附加到GitHub Issue中。 + 刷新 + 恶意插件可能会读取所有登录的账号或执行任意代码。\n\n信任此插件代表您愿意承担上述风险。 + 分享封面 + 更新 + 打开全局搜索 + 将详细日志打印到系统日志(会降低应用性能) + 打开最后阅读的章节 + 打开 插件/迁移 菜单 + 双击放大 + 未选中 + 已选中 + 旧版安装程序 + Webtoon + 可能会修复下载的章节同名时相互冲突的问题 + 裁剪边缘(条漫) + 自定义硬件位图阈值 + 数据与存储 + 如果阅读器加载空白图像,请逐步降低阈值。\n已选择: %s + 存储位置 + 详细日志记录 + 将作品移到底部 + 前进 + 发生意外错误 + 刚才 + 内部错误:%s + 重启应用 + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml b/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml index 0eaa5972aa..35c116d582 100644 --- a/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml +++ b/i18n/src/commonMain/moko-resources/zh-rTW/plurals.xml @@ -1,101 +1,74 @@ - %d 個擴充套件可更新 - %d 個分類 - 歷時 %1$s,出現 %2$s 個錯誤 - 略過了 %d 章,也許是來源沒有這些章節,或其已被篩選規則排除 - 共 %1$s 章 - - - 餘 %1$d 頁 - - 已清除快取,%d 個檔案已被刪除 - 移除 %1$d 個已下載的章節? - %1$s 分鐘後 - 共 %d 本漫畫 - 還有 %1$d 章 - 第%1$s章已從來源中刪除: \n%2$s \n是否刪除其下載内容? - 接下來未讀的 %d 章 - %d 個來源 - %d 種語言 - 有%d個更新正在等待 - 更新了%d個擴充套件 - 確定要遷移 %1$d%2$s 個漫畫系列嗎? - 已成功遷移 %d 個漫畫系列 - 確定要複製 %1$d%2$s 個漫畫系列嗎? - %d 系列類型 - %d 狀態 - %1$d 頁 - 清理完成。已刪除 %d 個資料夾 - + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml b/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml index 036ac8364a..bfb0eb968d 100644 --- a/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml @@ -453,10 +453,6 @@ 以下章節已讀 最後閱讀的章節%1$s 新章節 - 為了讓您順利下載漫畫章節、自動進行資料備份,以及閱讀儲存在本機的漫畫,TachiyomiJ2K 需要您授予其在 Android 11 環境下的「全檔案存取」權限。 -\n -\n接下來,在出現的畫面中,請啟動「允許存取並管理所有檔案」選項。 - Tachiyomi 需要有儲存空間存取權限才能下載漫畫。請允許\"管理儲存空間\" 入門指南 協助翻譯 搜尋擴充套件… @@ -574,7 +570,6 @@ 所有來源 隱藏來源 按住不放也可以重設章節歷史 - 需要檔案權限 永遠顯示目前分類 書櫃分組… 來源未安裝 @@ -674,7 +669,6 @@ 包含在全域性更新中 全域性更新 如果在這裡禁用某些按鈕,可以在其他地方找到 - 不支援 RARv5 格式 預先下載 閱讀時自動下載 已下載章數 @@ -963,4 +957,22 @@ 偵錯資訊 背景活動 分享封面 - + 更多選擇 + 返回 + 歡迎! + 先選擇一些預設值,您可以隨時在稍後的設定中更改這些東西 + 開始使用 + 打開隨機作品 + 顯示下載佇列 + 打開最後閱讀的章節 + 打開全局搜尋 + 邊緣裁剪(長圖) + 無效位置 + 已選中 + 長圖 + 重啟應用程式 + 刷新 + 預設 + 未選中 + 更新 + \ No newline at end of file From 84d2924a828b63d2725bf7eac0c88e0b69d8d099 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 2 Jan 2025 08:29:30 +0700 Subject: [PATCH 066/181] chore(archive): Mark as internal or private --- .../src/main/java/yokai/core/archive/ArchiveInputStream.kt | 3 ++- .../archive/src/main/java/yokai/core/archive/ArchiveReader.kt | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/archive/src/main/java/yokai/core/archive/ArchiveInputStream.kt b/core/archive/src/main/java/yokai/core/archive/ArchiveInputStream.kt index 5cd17aa026..7c4d7843d3 100644 --- a/core/archive/src/main/java/yokai/core/archive/ArchiveInputStream.kt +++ b/core/archive/src/main/java/yokai/core/archive/ArchiveInputStream.kt @@ -7,8 +7,9 @@ import me.zhanghai.android.libarchive.Archive import me.zhanghai.android.libarchive.ArchiveEntry import me.zhanghai.android.libarchive.ArchiveException -class ArchiveInputStream(buffer: Long, size: Long) : InputStream() { +internal class ArchiveInputStream(buffer: Long, size: Long) : InputStream() { private val lock = Any() + @Volatile private var isClosed = false diff --git a/core/archive/src/main/java/yokai/core/archive/ArchiveReader.kt b/core/archive/src/main/java/yokai/core/archive/ArchiveReader.kt index 97584cc08f..d9a4a42298 100644 --- a/core/archive/src/main/java/yokai/core/archive/ArchiveReader.kt +++ b/core/archive/src/main/java/yokai/core/archive/ArchiveReader.kt @@ -8,8 +8,8 @@ import java.io.InputStream import me.zhanghai.android.libarchive.ArchiveException class ArchiveReader(pfd: ParcelFileDescriptor) : Closeable { - val size = pfd.statSize - val address = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, pfd.fileDescriptor, 0) + private val size = pfd.statSize + private val address = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, pfd.fileDescriptor, 0) fun useEntries(block: (Sequence) -> T): T = ArchiveInputStream(address, size).use { block(generateSequence { it.getNextEntry() }) From fc87410d46ed4a44a44ad74870c9fe13214581b0 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 2 Jan 2025 09:05:00 +0700 Subject: [PATCH 067/181] fix(download): Making sure archive tmp file is created properly --- .../java/eu/kanade/tachiyomi/data/download/Downloader.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index fd0960f179..2739920559 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -604,8 +604,9 @@ class Downloader( dirname: String, tmpDir: UniFile, ) { - val zip = mangaDir.createFile("$dirname.cbz$TMP_DIR_SUFFIX")!! - ZipWriter(context, zip).use { writer -> + val zip = mangaDir.createFile("$dirname.cbz$TMP_DIR_SUFFIX") + if (zip?.isFile != true) throw Exception("Failed to create CBZ file for downloaded chapter") + ZipWriter(context, zip!!).use { writer -> tmpDir.listFiles()?.forEach { file -> writer.write(file) } From 1a16d84e61bc9f09a6b22c9ed5f37d2ef5d0e96d Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 2 Jan 2025 10:05:26 +0700 Subject: [PATCH 068/181] refactor(archive): Turn timespec function to extension method --- .../archive/src/main/java/yokai/core/archive/ZipWriter.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/archive/src/main/java/yokai/core/archive/ZipWriter.kt b/core/archive/src/main/java/yokai/core/archive/ZipWriter.kt index 3ee02b02b7..9a776e106c 100644 --- a/core/archive/src/main/java/yokai/core/archive/ZipWriter.kt +++ b/core/archive/src/main/java/yokai/core/archive/ZipWriter.kt @@ -65,10 +65,10 @@ private fun StructStat.toArchiveStat() = ArchiveEntry.StructStat().apply { stSize = st_size stBlksize = st_blksize stBlocks = st_blocks - stAtim = timespec(st_atime) - stMtim = timespec(st_mtime) - stCtim = timespec(st_ctime) + stAtim = st_atime.toTimespec() + stMtim = st_mtime.toTimespec() + stCtim = st_ctime.toTimespec() stIno = st_ino } -private fun timespec(tvSec: Long) = ArchiveEntry.StructTimespec().also { it.tvSec = tvSec } +private fun Long.toTimespec() = ArchiveEntry.StructTimespec().also { it.tvSec = this } From 49b10c1b4fd824b394c240cabe074b1d9f77edd9 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 2 Jan 2025 21:42:53 +0700 Subject: [PATCH 069/181] refactor: Rework Dialog --- .../base/controller/BaseComposeController.kt | 13 ++- .../kanade/tachiyomi/ui/main/MainActivity.kt | 15 +++- .../tachiyomi/ui/more/AboutController.kt | 26 +++--- .../ui/setting/SettingsComposeController.kt | 11 +-- .../kanade/tachiyomi/util/compose/Locals.kt | 4 +- .../yokai/domain/ComposableAlertDialog.kt | 10 --- .../main/java/yokai/domain/DialogHostState.kt | 28 +++++++ .../extension/repo/ExtensionRepoController.kt | 17 +--- .../extension/repo/ExtensionRepoScreen.kt | 65 ++++++++------- .../settings/SettingsCommonWidget.kt | 6 +- .../settings/screen/SettingsDataScreen.kt | 21 ++--- .../settings/screen/about/AboutDialogs.kt | 83 +++++++++++++++++++ .../settings/screen/about/AboutScreen.kt | 21 ++--- .../settings/screen/data/AlertDialogs.kt | 33 ++++---- 14 files changed, 223 insertions(+), 130 deletions(-) delete mode 100644 app/src/main/java/yokai/domain/ComposableAlertDialog.kt create mode 100644 app/src/main/java/yokai/domain/DialogHostState.kt create mode 100644 app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt index 8f78c042ec..7f2fed8f1f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt @@ -5,8 +5,13 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy +import eu.kanade.tachiyomi.util.compose.LocalBackPress +import eu.kanade.tachiyomi.util.compose.LocalDialogHostState +import yokai.domain.DialogHostState import yokai.presentation.theme.YokaiTheme abstract class BaseComposeController(bundle: Bundle? = null) : @@ -25,8 +30,14 @@ abstract class BaseComposeController(bundle: Bundle? = null) : ) setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { + val dialogHostState = remember { DialogHostState() } YokaiTheme { - ScreenContent() + CompositionLocalProvider( + LocalDialogHostState provides dialogHostState, + LocalBackPress provides router::handleBack, + ) { + ScreenContent() + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 67fb31da3e..28461ec9cd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -1014,7 +1014,12 @@ open class MainActivity : BaseActivity() { withContext(Dispatchers.Main) { showNotificationPermissionPrompt() AppUpdateNotifier.releasePageUrl = result.release.releaseLink - AboutController.NewUpdateDialogController(body, url, isBeta).showDialog(router) + if ( + // FIXME: Show Compose version of NewUpdateDialog for AboutController + router.backstack.lastOrNull()?.controller !is AboutController + ) { + AboutController.NewUpdateDialogController(body, url, isBeta).showDialog(router) + } } } } catch (error: Exception) { @@ -1037,6 +1042,7 @@ open class MainActivity : BaseActivity() { @SuppressLint("MissingSuperCall") override fun onNewIntent(intent: Intent) { + splashState.ready = true if (!handleIntentAction(intent)) { super.onNewIntent(intent) } @@ -1092,7 +1098,11 @@ open class MainActivity : BaseActivity() { SHORTCUT_UPDATE_NOTES -> { val extras = intent.extras ?: return false if (router.backstack.isEmpty()) nav.selectedItemId = R.id.nav_library - if (router.backstack.lastOrNull()?.controller !is AboutController.NewUpdateDialogController) { + if ( + router.backstack.lastOrNull()?.controller !is AboutController.NewUpdateDialogController && + // FIXME: Show Compose version of NewUpdateDialog for AboutController + router.backstack.lastOrNull()?.controller !is AboutController + ) { AboutController.NewUpdateDialogController(extras).showDialog(router) } } @@ -1122,7 +1132,6 @@ open class MainActivity : BaseActivity() { else -> return false } - splashState.ready = true return true } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt index 4c55cbef88..f830d0ff81 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt @@ -1,28 +1,24 @@ package eu.kanade.tachiyomi.ui.more import android.app.Dialog +import android.content.Context import android.os.Build import android.os.Bundle +import android.text.Spanned import android.text.method.LinkMovementMethod import android.view.View import android.widget.TextView import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.transitions.CrossfadeTransition import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController import eu.kanade.tachiyomi.ui.base.controller.DialogController -import eu.kanade.tachiyomi.util.compose.LocalAlertDialog -import eu.kanade.tachiyomi.util.compose.LocalBackPress -import eu.kanade.tachiyomi.util.compose.LocalRouter import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.view.setNegativeButton import eu.kanade.tachiyomi.util.view.setPositiveButton import eu.kanade.tachiyomi.util.view.setTitle import io.noties.markwon.Markwon -import yokai.domain.ComposableAlertDialog import yokai.i18n.MR import yokai.presentation.settings.screen.about.AboutScreen import android.R as AR @@ -34,17 +30,12 @@ class AboutController : BaseComposeController() { Navigator( screen = AboutScreen(), content = { - CompositionLocalProvider( - LocalAlertDialog provides ComposableAlertDialog(null), - LocalBackPress provides router::handleBack, - LocalRouter provides router, - ) { - CrossfadeTransition(navigator = it) - } + CrossfadeTransition(navigator = it) }, ) } + @Deprecated("Use [DialogHostState.showNewUpdateDialog] instead", ReplaceWith("DialogHostState.showNewUpdateDialog()")) class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) { constructor(body: String, url: String, isBeta: Boolean?) : this( @@ -56,9 +47,7 @@ class AboutController : BaseComposeController() { ) override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val releaseBody = (args.getString(BODY_KEY) ?: "") - .replace("""---(\R|.)*Checksums(\R|.)*""".toRegex(), "") - val info = Markwon.create(activity!!).toMarkdown(releaseBody) + val info = activity!!.parseReleaseNotes(args.getString(BODY_KEY) ?: "") val isOnA12 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S val isBeta = args.getBoolean(IS_BETA, false) @@ -96,3 +85,8 @@ class AboutController : BaseComposeController() { } } } + +fun Context.parseReleaseNotes(releaseNotes: String): Spanned { + val releaseBody = releaseNotes.replace("""---(\R|.)*Checksums(\R|.)*""".toRegex(), "") + return Markwon.create(this).toMarkdown(releaseBody) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsComposeController.kt index 9912f988fe..0aeed45248 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsComposeController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsComposeController.kt @@ -1,11 +1,7 @@ package eu.kanade.tachiyomi.ui.setting import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController -import eu.kanade.tachiyomi.util.compose.LocalAlertDialog -import eu.kanade.tachiyomi.util.compose.LocalBackPress -import yokai.domain.ComposableAlertDialog import yokai.presentation.settings.ComposableSettings abstract class SettingsComposeController: BaseComposeController(), SettingsControllerInterface { @@ -18,11 +14,6 @@ abstract class SettingsComposeController: BaseComposeController(), SettingsContr @Composable override fun ScreenContent() { - CompositionLocalProvider( - LocalAlertDialog provides ComposableAlertDialog(null), - LocalBackPress provides router::handleBack, - ) { - getComposableSettings().Content() - } + getComposableSettings().Content() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/compose/Locals.kt b/app/src/main/java/eu/kanade/tachiyomi/util/compose/Locals.kt index 54a1468710..af73accc34 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/compose/Locals.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/compose/Locals.kt @@ -5,14 +5,14 @@ import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf import com.bluelinelabs.conductor.Router -import yokai.domain.ComposableAlertDialog +import yokai.domain.DialogHostState val ProvidableCompositionLocal.currentOrThrow @Composable get(): T = this.current ?: throw RuntimeException("CompositionLocal is null") val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null } -val LocalAlertDialog: ProvidableCompositionLocal = compositionLocalOf { null } +val LocalDialogHostState: ProvidableCompositionLocal = compositionLocalOf { null } @Deprecated( message = "Scheduled for removal once Conductor is fully replaced by Voyager", replaceWith = ReplaceWith("LocalNavigator", "cafe.adriel.voyager.navigator.LocalNavigator"), diff --git a/app/src/main/java/yokai/domain/ComposableAlertDialog.kt b/app/src/main/java/yokai/domain/ComposableAlertDialog.kt deleted file mode 100644 index 9be777dd38..0000000000 --- a/app/src/main/java/yokai/domain/ComposableAlertDialog.kt +++ /dev/null @@ -1,10 +0,0 @@ -package yokai.domain - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue - -class ComposableAlertDialog(initial: (@Composable () -> Unit)?) { - var content: (@Composable () -> Unit)? by mutableStateOf(initial) -} diff --git a/app/src/main/java/yokai/domain/DialogHostState.kt b/app/src/main/java/yokai/domain/DialogHostState.kt new file mode 100644 index 0000000000..4c53b460c8 --- /dev/null +++ b/app/src/main/java/yokai/domain/DialogHostState.kt @@ -0,0 +1,28 @@ +package yokai.domain + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +typealias ComposableDialog = (@Composable () -> Unit)? +typealias ComposableDialogState = MutableState + +class DialogHostState(initial: ComposableDialog = null) : ComposableDialogState by mutableStateOf(initial) { + val mutex = Mutex() + + fun closeDialog() { + value = null + } + + suspend inline fun dialog(crossinline dialog: @Composable (CancellableContinuation) -> Unit) = mutex.withLock { + try { + suspendCancellableCoroutine { cont -> value = { dialog(cont) } } + } finally { + closeDialog() + } + } +} diff --git a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoController.kt b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoController.kt index f99b28c24a..109f6858df 100644 --- a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoController.kt +++ b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoController.kt @@ -1,11 +1,7 @@ package yokai.presentation.extension.repo import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController -import eu.kanade.tachiyomi.util.compose.LocalAlertDialog -import eu.kanade.tachiyomi.util.compose.LocalBackPress -import yokai.domain.ComposableAlertDialog class ExtensionRepoController() : BaseComposeController() { @@ -18,14 +14,9 @@ class ExtensionRepoController() : @Composable override fun ScreenContent() { - CompositionLocalProvider( - LocalAlertDialog provides ComposableAlertDialog(null), - LocalBackPress provides router::handleBack, - ) { - ExtensionRepoScreen( - title = "Extension Repos", - repoUrl = repoUrl, - ) - } + ExtensionRepoScreen( + title = "Extension Repos", + repoUrl = repoUrl, + ) } } diff --git a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt index ed93b2b846..2e347e2397 100644 --- a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt +++ b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -26,13 +27,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import dev.icerock.moko.resources.compose.stringResource -import eu.kanade.tachiyomi.util.compose.LocalAlertDialog import eu.kanade.tachiyomi.util.compose.LocalBackPress +import eu.kanade.tachiyomi.util.compose.LocalDialogHostState import eu.kanade.tachiyomi.util.compose.currentOrThrow import eu.kanade.tachiyomi.util.isTablet import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.flow.collectLatest -import yokai.domain.ComposableAlertDialog +import kotlinx.coroutines.launch +import yokai.domain.DialogHostState import yokai.domain.extension.repo.model.ExtensionRepo import yokai.i18n.MR import yokai.presentation.AppBarType @@ -51,10 +53,12 @@ fun ExtensionRepoScreen( ) { val onBackPress = LocalBackPress.currentOrThrow val context = LocalContext.current + val alertDialog = LocalDialogHostState.currentOrThrow + val scope = rememberCoroutineScope() + val repoState by viewModel.repoState.collectAsState() var inputText by remember { mutableStateOf("") } val listState = rememberLazyListState() - val alertDialog = LocalAlertDialog.currentOrThrow YokaiScaffold( onNavigationIconClicked = onBackPress, @@ -79,7 +83,7 @@ fun ExtensionRepoScreen( val repos = (repoState as ExtensionRepoState.Success).repos - alertDialog.content?.let { it() } + alertDialog.value?.invoke() LazyColumn( modifier = Modifier.padding(innerPadding), @@ -113,7 +117,7 @@ fun ExtensionRepoScreen( ExtensionRepoItem( extensionRepo = repo, onDeleteClick = { repoToDelete -> - alertDialog.content = { ExtensionRepoDeletePrompt(repoToDelete, alertDialog, viewModel) } + scope.launch { alertDialog.awaitExtensionRepoDeletePrompt(repoToDelete, viewModel) } }, ) } @@ -127,46 +131,45 @@ fun ExtensionRepoScreen( LaunchedEffect(Unit) { viewModel.event.collectLatest { event -> - if (event is ExtensionRepoEvent.LocalizedMessage) - context.toast(event.stringRes) - if (event is ExtensionRepoEvent.Success) - inputText = "" - if (event is ExtensionRepoEvent.ShowDialog) - alertDialog.content = { - if (event.dialog is RepoDialog.Conflict) { - ExtensionRepoReplacePrompt( - oldRepo = event.dialog.oldRepo, - newRepo = event.dialog.newRepo, - onDismissRequest = { alertDialog.content = null }, - onMigrate = { viewModel.replaceRepo(event.dialog.newRepo) }, - ) + when (event) { + is ExtensionRepoEvent.NoOp -> {} + is ExtensionRepoEvent.LocalizedMessage -> context.toast(event.stringRes) + is ExtensionRepoEvent.Success -> inputText = "" + is ExtensionRepoEvent.ShowDialog -> { + when(event.dialog) { + is RepoDialog.Conflict -> { + alertDialog.awaitExtensionRepoReplacePrompt( + oldRepo = event.dialog.oldRepo, + newRepo = event.dialog.newRepo, + onMigrate = { viewModel.replaceRepo(event.dialog.newRepo) }, + ) + } } } + } } } } -@Composable -fun ExtensionRepoReplacePrompt( +suspend fun DialogHostState.awaitExtensionRepoReplacePrompt( oldRepo: ExtensionRepo, newRepo: ExtensionRepo, - onDismissRequest: () -> Unit, onMigrate: () -> Unit, -) { +): Unit = dialog { cont -> AlertDialog( - onDismissRequest = onDismissRequest, + onDismissRequest = { cont.cancel() }, confirmButton = { TextButton( onClick = { onMigrate() - onDismissRequest() + cont.cancel() }, ) { Text(text = stringResource(MR.strings.action_replace_repo)) } }, dismissButton = { - TextButton(onClick = onDismissRequest) { + TextButton(onClick = { cont.cancel() }) { Text(text = stringResource(AR.string.cancel)) } }, @@ -179,8 +182,10 @@ fun ExtensionRepoReplacePrompt( ) } -@Composable -fun ExtensionRepoDeletePrompt(repoToDelete: String, alertDialog: ComposableAlertDialog, viewModel: ExtensionRepoViewModel) { +suspend fun DialogHostState.awaitExtensionRepoDeletePrompt( + repoToDelete: String, + viewModel: ExtensionRepoViewModel, +): Unit = dialog { cont -> AlertDialog( containerColor = MaterialTheme.colorScheme.surface, title = { @@ -199,12 +204,12 @@ fun ExtensionRepoDeletePrompt(repoToDelete: String, alertDialog: ComposableAlert fontSize = 14.sp, ) }, - onDismissRequest = { alertDialog.content = null }, + onDismissRequest = { cont.cancel() }, confirmButton = { TextButton( onClick = { viewModel.deleteRepo(repoToDelete) - alertDialog.content = null + cont.cancel() } ) { Text( @@ -215,7 +220,7 @@ fun ExtensionRepoDeletePrompt(repoToDelete: String, alertDialog: ComposableAlert } }, dismissButton = { - TextButton(onClick = { alertDialog.content = null }) { + TextButton(onClick = { cont.cancel() }) { Text( text = stringResource(MR.strings.cancel), color = MaterialTheme.colorScheme.primary, diff --git a/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt b/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt index d4a02c21ac..34feac3f93 100644 --- a/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt +++ b/app/src/main/java/yokai/presentation/settings/SettingsCommonWidget.kt @@ -17,8 +17,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed import eu.kanade.tachiyomi.core.storage.preference.collectAsState import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.util.compose.LocalAlertDialog import eu.kanade.tachiyomi.util.compose.LocalBackPress +import eu.kanade.tachiyomi.util.compose.LocalDialogHostState import eu.kanade.tachiyomi.util.compose.currentOrThrow import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.delay @@ -44,7 +44,7 @@ fun SettingsScaffold( val preferences: PreferencesHelper by injectLazy() val useLargeAppBar by preferences.useLargeToolbar().collectAsState() val onBackPress = LocalBackPress.currentOrThrow - val alertDialog = LocalAlertDialog.currentOrThrow + val alertDialog = LocalDialogHostState.currentOrThrow YokaiScaffold( onNavigationIconClicked = onBackPress, @@ -54,7 +54,7 @@ fun SettingsScaffold( scrollBehavior = appBarScrollBehavior, snackbarHost = snackbarHost, ) { innerPadding -> - alertDialog.content?.let { it() } + alertDialog.value?.invoke() content(innerPadding) } diff --git a/app/src/main/java/yokai/presentation/settings/screen/SettingsDataScreen.kt b/app/src/main/java/yokai/presentation/settings/screen/SettingsDataScreen.kt index 0ac654961a..4fba963c21 100644 --- a/app/src/main/java/yokai/presentation/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/SettingsDataScreen.kt @@ -33,11 +33,10 @@ import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.extension.ExtensionManager -import eu.kanade.tachiyomi.util.compose.LocalAlertDialog +import eu.kanade.tachiyomi.util.compose.LocalDialogHostState import eu.kanade.tachiyomi.util.compose.currentOrThrow import eu.kanade.tachiyomi.util.relativeTimeSpanString import eu.kanade.tachiyomi.util.system.DeviceUtil -import eu.kanade.tachiyomi.util.system.e import eu.kanade.tachiyomi.util.system.launchNonCancellableIO import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.withUIContext @@ -57,9 +56,9 @@ import yokai.presentation.component.preference.storageLocationText import yokai.presentation.component.preference.widget.BasePreferenceWidget import yokai.presentation.component.preference.widget.PrefsHorizontalPadding import yokai.presentation.settings.ComposableSettings -import yokai.presentation.settings.screen.data.CreateBackup -import yokai.presentation.settings.screen.data.RestoreBackup import yokai.presentation.settings.screen.data.StorageInfo +import yokai.presentation.settings.screen.data.awaitCreateBackup +import yokai.presentation.settings.screen.data.awaitRestoreBackup import yokai.presentation.settings.screen.data.storageLocationPicker import yokai.util.lang.getString @@ -101,7 +100,7 @@ object SettingsDataScreen : ComposableSettings { private fun getBackupAndRestoreGroup(backupPreferences: BackupPreferences): Preference.PreferenceGroup { val scope = rememberCoroutineScope() val context = LocalContext.current - val alertDialog = LocalAlertDialog.currentOrThrow + val alertDialog = LocalDialogHostState.currentOrThrow val extensionManager = remember { Injekt.get() } val storageManager = remember { Injekt.get() } @@ -122,14 +121,11 @@ object SettingsDataScreen : ComposableSettings { Pair(null, e) } - alertDialog.content = { - RestoreBackup( + scope.launch { + alertDialog.awaitRestoreBackup( context = context, uri = it, pair = results, - onDismissRequest = { - alertDialog.content = null - } ) } } @@ -166,11 +162,10 @@ object SettingsDataScreen : ComposableSettings { return@SegmentedButton } - alertDialog.content = { - CreateBackup( + scope.launch { + alertDialog.awaitCreateBackup( context = context, uri = dir.uri, - onDismissRequest = { alertDialog.content = null }, ) } } else { diff --git a/app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt b/app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt new file mode 100644 index 0000000000..9f01d9db8d --- /dev/null +++ b/app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt @@ -0,0 +1,83 @@ +package yokai.presentation.settings.screen.about + +import android.os.Build +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import com.google.android.material.textview.MaterialTextView +import dev.icerock.moko.resources.compose.stringResource +import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob +import eu.kanade.tachiyomi.ui.more.parseReleaseNotes +import java.io.Serializable +import yokai.domain.DialogHostState +import yokai.i18n.MR + +data class NewUpdateData( + val body: String, + val url: String, + val isBeta: Boolean?, +) : Serializable + +suspend fun DialogHostState.awaitNewUpdateDialog( + data: NewUpdateData, + onDismiss: () -> Unit = {}, +): Unit = dialog { cont -> + val context = LocalContext.current + val appContext = context.applicationContext + + val isOnA12 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + + AlertDialog( + onDismissRequest = { + onDismiss() + cont.cancel() + }, + title = { + Text( + text = stringResource( + if (data.isBeta == true) { + MR.strings.new_beta_version_available + } else { + MR.strings.new_version_available + } + ) + ) + }, + confirmButton = { + TextButton(onClick = { + AppDownloadInstallJob.start(appContext, data.url, true) + onDismiss() + cont.cancel() + }) { + Text(text = stringResource(if (isOnA12) MR.strings.update else MR.strings.download)) + } + }, + dismissButton = { + TextButton( + onClick = { + onDismiss() + cont.cancel() + } + ) { + Text(text = stringResource(MR.strings.ignore)) + } + }, + text = { MarkdownText(data.body) } + ) +} + +@Composable +private fun MarkdownText(text: String) { + val context = LocalContext.current + AndroidView( + factory = { + MaterialTextView(it) + }, + update = { + it.text = context.parseReleaseNotes(text) + }, + ) +} diff --git a/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt index 66d57832dc..a541e73609 100644 --- a/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt @@ -27,7 +27,6 @@ import androidx.compose.ui.unit.dp import androidx.core.content.getSystemService import cafe.adriel.voyager.navigator.LocalNavigator import co.touchlab.kermit.Logger -import com.bluelinelabs.conductor.Router import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.core.storage.preference.asDateFormat @@ -36,9 +35,8 @@ import eu.kanade.tachiyomi.data.updater.AppUpdateChecker import eu.kanade.tachiyomi.data.updater.AppUpdateNotifier import eu.kanade.tachiyomi.data.updater.AppUpdateResult import eu.kanade.tachiyomi.data.updater.RELEASE_URL -import eu.kanade.tachiyomi.ui.more.AboutController.NewUpdateDialogController import eu.kanade.tachiyomi.util.CrashLogUtil -import eu.kanade.tachiyomi.util.compose.LocalRouter +import eu.kanade.tachiyomi.util.compose.LocalDialogHostState import eu.kanade.tachiyomi.util.compose.currentOrThrow import eu.kanade.tachiyomi.util.lang.toTimestampString import eu.kanade.tachiyomi.util.system.isOnline @@ -52,6 +50,7 @@ import java.util.Locale import java.util.TimeZone import kotlinx.coroutines.launch import uy.kohesive.injekt.injectLazy +import yokai.domain.DialogHostState import yokai.i18n.MR import yokai.presentation.component.preference.widget.TextPreferenceWidget import yokai.presentation.core.components.LinkIcon @@ -68,7 +67,7 @@ class AboutScreen : Screen() { override fun Content() { val context = LocalContext.current val navigator = LocalNavigator.currentOrThrow - val router = LocalRouter.currentOrThrow + val dialogHostState = LocalDialogHostState.currentOrThrow val uriHandler = LocalUriHandler.current val snackbarHostState = remember { SnackbarHostState() } @@ -109,7 +108,7 @@ class AboutScreen : Screen() { onPreferenceClick = { if (context.isOnline()) { scope.launch { - context.checkVersion(router) + context.checkVersion(dialogHostState) } } else { context.toast(MR.strings.no_network_connection) @@ -193,7 +192,7 @@ class AboutScreen : Screen() { else -> "Release ${BuildConfig.VERSION_NAME}" } - private suspend fun Context.checkVersion(router: Router) { + private suspend fun Context.checkVersion(dialogState: DialogHostState) { val updateChecker = AppUpdateChecker() withUIContext { toast(MR.strings.searching_for_updates) } @@ -208,14 +207,16 @@ class AboutScreen : Screen() { } when (result) { is AppUpdateResult.NewUpdate -> { - val body = result.release.info - val url = result.release.downloadLink - val isBeta = result.release.preRelease == true + val data = NewUpdateData( + result.release.info, + result.release.downloadLink, + result.release.preRelease == true + ) // Create confirmation window withUIContext { AppUpdateNotifier.releasePageUrl = result.release.releaseLink - NewUpdateDialogController(body, url, isBeta).showDialog(router) + dialogState.awaitNewUpdateDialog(data) } } is AppUpdateResult.NoNewUpdate -> { diff --git a/app/src/main/java/yokai/presentation/settings/screen/data/AlertDialogs.kt b/app/src/main/java/yokai/presentation/settings/screen/data/AlertDialogs.kt index 29b62334a6..809fff60fb 100644 --- a/app/src/main/java/yokai/presentation/settings/screen/data/AlertDialogs.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/data/AlertDialogs.kt @@ -8,10 +8,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import com.hippo.unifile.UniFile @@ -22,17 +20,16 @@ import eu.kanade.tachiyomi.data.backup.create.BackupOptions import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob import eu.kanade.tachiyomi.util.system.toast +import yokai.domain.DialogHostState import yokai.i18n.MR import yokai.presentation.component.LabeledCheckbox import android.R as AR -@Composable -fun RestoreBackup( +suspend fun DialogHostState.awaitRestoreBackup( context: Context, uri: Uri, pair: Pair, - onDismissRequest: () -> Unit, -) { +): Unit = dialog { cont -> val (results, e) = pair if (results != null) { var message = stringResource(MR.strings.restore_content_full) @@ -52,20 +49,20 @@ fun RestoreBackup( } AlertDialog( - onDismissRequest = onDismissRequest, + onDismissRequest = { cont.cancel() }, confirmButton = { TextButton( onClick = { context.toast(MR.strings.restoring_backup) BackupRestoreJob.start(context, uri) - onDismissRequest() + cont.cancel() }, ) { Text(text = stringResource(MR.strings.restore)) } }, dismissButton = { - TextButton(onClick = onDismissRequest) { + TextButton(onClick = { cont.cancel() }) { Text(text = stringResource(AR.string.cancel)) } }, @@ -74,9 +71,9 @@ fun RestoreBackup( ) } else { AlertDialog( - onDismissRequest = onDismissRequest, + onDismissRequest = { cont.cancel() }, confirmButton = { - TextButton(onClick = onDismissRequest) { + TextButton(onClick = { cont.cancel() }) { Text(text = stringResource(AR.string.cancel)) } }, @@ -86,29 +83,27 @@ fun RestoreBackup( } } -@Composable -fun CreateBackup( +suspend fun DialogHostState.awaitCreateBackup( context: Context, uri: Uri, - onDismissRequest: () -> Unit, -) { - var options by remember { mutableStateOf(BackupOptions()) } +): Unit = dialog { cont -> + var options by mutableStateOf(BackupOptions()) AlertDialog( - onDismissRequest = onDismissRequest, + onDismissRequest = { cont.cancel() }, confirmButton = { TextButton(onClick = { val actualUri = UniFile.fromUri(context, uri)?.createFile(Backup.getBackupFilename())?.uri ?: return@TextButton context.toast(MR.strings.creating_backup) BackupCreatorJob.startNow(context, actualUri, options) - onDismissRequest() + cont.cancel() }) { Text(stringResource(MR.strings.create)) } }, dismissButton = { - TextButton(onClick = { onDismissRequest() }) { + TextButton(onClick = { cont.cancel() }) { Text(stringResource(MR.strings.cancel)) } }, From a554c079fbbf35c8a718d16af5b66e7c8f3e0516 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 2 Jan 2025 22:01:37 +0700 Subject: [PATCH 070/181] fix: Handle update checker separately for AboutController for noe --- .../java/eu/kanade/tachiyomi/ui/main/MainActivity.kt | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 28461ec9cd..53cfe06c78 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -1001,7 +1001,8 @@ open class MainActivity : BaseActivity() { } private fun checkForAppUpdates() { - if (isUpdaterEnabled) { + // FIXME: Show Compose version of NewUpdateDialog for AboutController + if (isUpdaterEnabled && router.backstack.lastOrNull()?.controller !is AboutController) { lifecycleScope.launchIO { try { val result = updateChecker.checkForUpdate(this@MainActivity) @@ -1014,12 +1015,7 @@ open class MainActivity : BaseActivity() { withContext(Dispatchers.Main) { showNotificationPermissionPrompt() AppUpdateNotifier.releasePageUrl = result.release.releaseLink - if ( - // FIXME: Show Compose version of NewUpdateDialog for AboutController - router.backstack.lastOrNull()?.controller !is AboutController - ) { - AboutController.NewUpdateDialogController(body, url, isBeta).showDialog(router) - } + AboutController.NewUpdateDialogController(body, url, isBeta).showDialog(router) } } } catch (error: Exception) { From eba5aa1d2e5453979cf9b8279ba4a2ab097febbb Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 3 Jan 2025 08:40:46 +0700 Subject: [PATCH 071/181] fix: Make the markdown text scrollable --- .../presentation/settings/screen/about/AboutDialogs.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt b/app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt index 9f01d9db8d..58d2c2895d 100644 --- a/app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt @@ -1,10 +1,14 @@ package yokai.presentation.settings.screen.about import android.os.Build +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import com.google.android.material.textview.MaterialTextView @@ -65,7 +69,11 @@ suspend fun DialogHostState.awaitNewUpdateDialog( Text(text = stringResource(MR.strings.ignore)) } }, - text = { MarkdownText(data.body) } + text = { + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + MarkdownText(data.body) + } + } ) } From e06b28a60e5ac77bf023116e4a1a315f782bca8f Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 3 Jan 2025 09:26:49 +0700 Subject: [PATCH 072/181] fix: Handle version check for AboutController --- .../kanade/tachiyomi/ui/main/MainActivity.kt | 7 +-- .../onboarding/steps/PermissionStep.kt | 56 +++++++----------- .../settings/screen/about/AboutDialogs.kt | 17 ++++++ .../settings/screen/about/AboutScreen.kt | 59 +++++++++++++++---- 4 files changed, 90 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 53cfe06c78..cc0ecb6cc5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -117,6 +117,7 @@ import eu.kanade.tachiyomi.util.system.prepareSideNavContext import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.tryTakePersistableUriPermission +import eu.kanade.tachiyomi.util.system.withUIContext import eu.kanade.tachiyomi.util.view.BackHandlerControllerInterface import eu.kanade.tachiyomi.util.view.backgroundColor import eu.kanade.tachiyomi.util.view.blurBehindWindow @@ -136,12 +137,10 @@ import kotlin.collections.set import kotlin.math.abs import kotlin.math.min import kotlin.math.roundToLong -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.withContext import uy.kohesive.injekt.injectLazy import yokai.core.migration.Migrator import yokai.domain.base.BasePreferences @@ -198,6 +197,7 @@ open class MainActivity : BaseActivity() { dimenW to dimenH } + @Deprecated("Create contract directly from Composable") private val requestNotificationPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (!isGranted) { @@ -1001,7 +1001,6 @@ open class MainActivity : BaseActivity() { } private fun checkForAppUpdates() { - // FIXME: Show Compose version of NewUpdateDialog for AboutController if (isUpdaterEnabled && router.backstack.lastOrNull()?.controller !is AboutController) { lifecycleScope.launchIO { try { @@ -1012,7 +1011,7 @@ open class MainActivity : BaseActivity() { val isBeta = result.release.preRelease == true // Create confirmation window - withContext(Dispatchers.Main) { + withUIContext { showNotificationPermissionPrompt() AppUpdateNotifier.releasePageUrl = result.release.releaseLink AboutController.NewUpdateDialogController(body, url, isBeta).showDialog(router) diff --git a/app/src/main/java/yokai/presentation/onboarding/steps/PermissionStep.kt b/app/src/main/java/yokai/presentation/onboarding/steps/PermissionStep.kt index 2f4aa9c3a5..27596046f2 100644 --- a/app/src/main/java/yokai/presentation/onboarding/steps/PermissionStep.kt +++ b/app/src/main/java/yokai/presentation/onboarding/steps/PermissionStep.kt @@ -21,23 +21,18 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.core.content.getSystemService -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.compose.LocalLifecycleOwner -import eu.kanade.tachiyomi.R +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect +import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.util.system.isShizukuInstalled import yokai.i18n.MR -import yokai.util.lang.getString -import dev.icerock.moko.resources.compose.stringResource import yokai.presentation.component.Gap import yokai.presentation.theme.Size @@ -53,35 +48,26 @@ internal class PermissionStep : OnboardingStep { @Composable override fun Content() { val context = LocalContext.current - val lifecycleOwner = LocalLifecycleOwner.current - DisposableEffect(lifecycleOwner.lifecycle) { - val observer = object : DefaultLifecycleObserver { - override fun onResume(owner: LifecycleOwner) { - installGranted = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.packageManager.canRequestPackageInstalls() - } else { - @Suppress("DEPRECATION") - Settings.Secure.getInt( - context.contentResolver, - Settings.Secure.INSTALL_NON_MARKET_APPS - ) != 0 - } || context.isShizukuInstalled - notificationGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == - PackageManager.PERMISSION_GRANTED - } else { - true - } - batteryGranted = context.getSystemService()!! - .isIgnoringBatteryOptimizations(context.packageName) - } - } - lifecycleOwner.lifecycle.addObserver(observer) - onDispose { - lifecycleOwner.lifecycle.removeObserver(observer) + LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { + installGranted = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + context.packageManager.canRequestPackageInstalls() + } else { + @Suppress("DEPRECATION") + Settings.Secure.getInt( + context.contentResolver, + Settings.Secure.INSTALL_NON_MARKET_APPS + ) != 0 + } || context.isShizukuInstalled + notificationGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == + PackageManager.PERMISSION_GRANTED + } else { + true } + batteryGranted = context.getSystemService()!! + .isIgnoringBatteryOptimizations(context.packageName) } Column( diff --git a/app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt b/app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt index 58d2c2895d..8fb10c81a8 100644 --- a/app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/about/AboutDialogs.kt @@ -10,14 +10,17 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.viewinterop.AndroidView import com.google.android.material.textview.MaterialTextView import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob import eu.kanade.tachiyomi.ui.more.parseReleaseNotes import java.io.Serializable +import kotlin.coroutines.resume import yokai.domain.DialogHostState import yokai.i18n.MR +import android.R as AR data class NewUpdateData( val body: String, @@ -89,3 +92,17 @@ private fun MarkdownText(text: String) { }, ) } + +suspend fun DialogHostState.awaitNotificationPermissionDeniedDialog(): Unit = dialog { cont -> + // cont.resume(Unit) so that new update dialog will be shown next + AlertDialog( + onDismissRequest = { if (cont.isActive) cont.resume(Unit) }, + title = { Text(text = stringResource(MR.strings.warning)) }, + text = { Text(text = stringResource(MR.strings.allow_notifications_recommended)) }, + confirmButton = { + TextButton(onClick = { if (cont.isActive) cont.resume(Unit) }) { + Text(text = stringResource(AR.string.ok)) + } + }, + ) +} diff --git a/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt index a541e73609..b120337de1 100644 --- a/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt @@ -4,6 +4,8 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.os.Build +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow @@ -25,8 +27,11 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.unit.dp import androidx.core.content.getSystemService +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect import cafe.adriel.voyager.navigator.LocalNavigator import co.touchlab.kermit.Logger +import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.core.storage.preference.asDateFormat @@ -39,7 +44,9 @@ import eu.kanade.tachiyomi.util.CrashLogUtil import eu.kanade.tachiyomi.util.compose.LocalDialogHostState import eu.kanade.tachiyomi.util.compose.currentOrThrow import eu.kanade.tachiyomi.util.lang.toTimestampString +import eu.kanade.tachiyomi.util.showNotificationPermissionPrompt import eu.kanade.tachiyomi.util.system.isOnline +import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.localeContext import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.withUIContext @@ -49,7 +56,8 @@ import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone import kotlinx.coroutines.launch -import uy.kohesive.injekt.injectLazy +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import yokai.domain.DialogHostState import yokai.i18n.MR import yokai.presentation.component.preference.widget.TextPreferenceWidget @@ -70,11 +78,34 @@ class AboutScreen : Screen() { val dialogHostState = LocalDialogHostState.currentOrThrow val uriHandler = LocalUriHandler.current + val preferences = remember { Injekt.get() } + val snackbarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() val listState = rememberLazyListState() - val preferences: PreferencesHelper by injectLazy() + val requestNotificationPermission = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (!isGranted) { + scope.launch { dialogHostState.awaitNotificationPermissionDeniedDialog() } + } + } + LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { + // FIXME: Move this to MainActivity once the app is fully migrated to Compose + scope.launchIO { + context.checkVersion( + dialogState = dialogHostState, + isUserPrompt = false, + notificationPrompt = { + context.showNotificationPermissionPrompt( + requestNotificationPermission, + false, + preferences, + ) + } + ) + } + } + val dateFormat by lazy { preferences.dateFormatRaw().get().asDateFormat() } SettingsScaffold( @@ -108,7 +139,7 @@ class AboutScreen : Screen() { onPreferenceClick = { if (context.isOnline()) { scope.launch { - context.checkVersion(dialogHostState) + context.checkVersion(dialogHostState, true) } } else { context.toast(MR.strings.no_network_connection) @@ -192,16 +223,25 @@ class AboutScreen : Screen() { else -> "Release ${BuildConfig.VERSION_NAME}" } - private suspend fun Context.checkVersion(dialogState: DialogHostState) { + private fun Context.toastIfNotUserPrompt(message: StringResource, isUserPrompt: Boolean) { + toastIfNotUserPrompt(getString(message), isUserPrompt) + } + + private fun Context.toastIfNotUserPrompt(message: String?, isUserPrompt: Boolean) { + if (!isUserPrompt) return + toast(message) + } + + private suspend fun Context.checkVersion(dialogState: DialogHostState, isUserPrompt: Boolean, notificationPrompt: () -> Unit = {}) { val updateChecker = AppUpdateChecker() - withUIContext { toast(MR.strings.searching_for_updates) } + withUIContext { toastIfNotUserPrompt(MR.strings.searching_for_updates, isUserPrompt) } val result = try { - updateChecker.checkForUpdate(this, true) + updateChecker.checkForUpdate(this, isUserPrompt) } catch (error: Exception) { withUIContext { - toast(error.message) + toastIfNotUserPrompt(error.message, isUserPrompt) Logger.e(error) { "Couldn't check new update" } } } @@ -215,14 +255,13 @@ class AboutScreen : Screen() { // Create confirmation window withUIContext { + if (!isUserPrompt) { notificationPrompt() } AppUpdateNotifier.releasePageUrl = result.release.releaseLink dialogState.awaitNewUpdateDialog(data) } } is AppUpdateResult.NoNewUpdate -> { - withUIContext { - toast(MR.strings.no_new_updates_available) - } + withUIContext { toastIfNotUserPrompt(MR.strings.no_new_updates_available, isUserPrompt) } } } } From a3672be7282c553017d35b310fb5a148d677ff14 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 3 Jan 2025 10:44:38 +0700 Subject: [PATCH 073/181] fix(myanimelist): Fix nullability and fallback to medium cover if large cover is null --- .../tachiyomi/data/track/myanimelist/MyAnimeListApi.kt | 2 +- .../kanade/tachiyomi/data/track/myanimelist/dto/MALManga.kt | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt index 9468ee359e..b3411cd2d7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt @@ -96,7 +96,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI title = it.title summary = it.synopsis total_chapters = it.numChapters - cover_url = it.covers.large + cover_url = (it.covers?.large ?: it.covers?.medium).orEmpty() tracking_url = "https://myanimelist.net/manga/$media_id" publishing_status = it.status.replace("_", " ") publishing_type = it.mediaType.replace("_", " ") diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALManga.kt index c4ab92ee92..6950c54960 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALManga.kt @@ -12,7 +12,7 @@ data class MALManga( val numChapters: Long, val mean: Double = -1.0, @SerialName("main_picture") - val covers: MALMangaCovers, + val covers: MALMangaCovers?, val status: String, @SerialName("media_type") val mediaType: String, @@ -22,5 +22,6 @@ data class MALManga( @Serializable data class MALMangaCovers( - val large: String = "", + val large: String?, + val medium: String, ) From e415fd4ef24c99ac3fb990b4316be23e37a7f9cb Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 3 Jan 2025 11:08:29 +0700 Subject: [PATCH 074/181] chore(about): Link weblate --- .../settings/screen/about/AboutScreen.kt | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt index b120337de1..77ff7c7cdd 100644 --- a/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt +++ b/app/src/main/java/yokai/presentation/settings/screen/about/AboutScreen.kt @@ -181,12 +181,26 @@ class AboutScreen : Screen() { HorizontalDivider() TextPreferenceWidget( - title = stringResource(MR.strings.open_source_licenses), - onPreferenceClick = { navigator.push(AboutLicenseScreen()) } + title = stringResource(MR.strings.help_translate), + onPreferenceClick = { uriHandler.openUri("https://hosted.weblate.org/engage/yokai/") }, ) } } + item { + TextPreferenceWidget( + title = stringResource(MR.strings.helpful_translation_links), + onPreferenceClick = { uriHandler.openUri("https://mihon.app/docs/contribute#helpful-links") }, + ) + } + + item { + TextPreferenceWidget( + title = stringResource(MR.strings.open_source_licenses), + onPreferenceClick = { navigator.push(AboutLicenseScreen()) }, + ) + } + item { FlowRow( modifier = From cae0332ef987db5d9976adcf9ba8a5bc4cd05ef1 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani <46041660+null2264@users.noreply.github.com> Date: Sun, 5 Jan 2025 18:15:34 +0700 Subject: [PATCH 075/181] refactor(library): Store sectioned library instead of flatten version of it (#336) * refactor(library): Store sectioned first before flattening it out * fix: Fix build, also rename some variables and move stuff around * chore: Replace findCurrentCategory with currentCategory getter * fix: Empty category can't be collapsed * chore: Disable file log for debug build * fix: Entry always displayed on default category * refactor: Specify id, source, and url directly from MangaImpl constructor * refactor: Make LibraryManga not extend MangaImpl * refactor: Separate placeholder from LibraryManga * fix: Default category should always be at the very beginning * fix: Accidentally made the entries invisible * fix: Default category disappear everytime a new category is added --- app/src/main/java/eu/kanade/tachiyomi/App.kt | 2 +- .../data/backup/models/BackupManga.kt | 7 +- .../data/database/models/Category.kt | 16 +- .../data/database/models/CategoryImpl.kt | 4 + .../data/database/models/LibraryManga.kt | 97 +-- .../tachiyomi/data/database/models/Manga.kt | 20 +- .../database/models/MangaChapterHistory.kt | 6 +- .../data/database/models/MangaImpl.kt | 14 +- .../data/library/CustomMangaManager.kt | 12 +- .../data/library/LibraryUpdateJob.kt | 89 +- .../data/library/LibraryUpdateNotifier.kt | 18 +- .../ui/library/LibraryCategoryAdapter.kt | 44 +- .../tachiyomi/ui/library/LibraryController.kt | 57 +- .../tachiyomi/ui/library/LibraryGridHolder.kt | 19 +- .../tachiyomi/ui/library/LibraryHolder.kt | 15 +- .../tachiyomi/ui/library/LibraryItem.kt | 173 +--- .../tachiyomi/ui/library/LibraryListHolder.kt | 42 +- .../tachiyomi/ui/library/LibraryMangaItem.kt | 180 +++++ .../ui/library/LibraryPlaceholderItem.kt | 57 ++ .../tachiyomi/ui/library/LibraryPresenter.kt | 762 +++++++++--------- .../ui/library/filter/FilterBottomSheet.kt | 6 +- .../ui/more/stats/StatsController.kt | 10 +- .../tachiyomi/ui/more/stats/StatsPresenter.kt | 6 +- .../stats/details/StatsDetailsPresenter.kt | 36 +- .../tachiyomi/ui/reader/ReaderViewModel.kt | 2 +- .../library/custom/model/CustomMangaInfo.kt | 3 +- .../tachiyomi/domain/manga/models/Manga.kt | 3 - 27 files changed, 899 insertions(+), 801 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaItem.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPlaceholderItem.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index b929105a1c..2fb9325bfd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -300,7 +300,7 @@ fun buildLogWritersToAdd( ) = buildList { if (!BuildConfig.DEBUG) add(CrashlyticsLogWriter()) - if (logPath != null) add(RollingUniFileLogWriter(logPath = logPath, isVerbose = isVerbose)) + if (logPath != null && !BuildConfig.DEBUG) add(RollingUniFileLogWriter(logPath = logPath, isVerbose = isVerbose)) } private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt index 00521bafe0..fc2f87bacf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt @@ -57,8 +57,10 @@ data class BackupManga( @ProtoNumber(805) var customGenre: List? = null, ) { fun getMangaImpl(): MangaImpl { - return MangaImpl().apply { - url = this@BackupManga.url + return MangaImpl( + source = this.source, + url = this.url, + ).apply { title = this@BackupManga.title artist = this@BackupManga.artist author = this@BackupManga.author @@ -67,7 +69,6 @@ data class BackupManga( status = this@BackupManga.status thumbnail_url = this@BackupManga.thumbnailUrl favorite = this@BackupManga.favorite - source = this@BackupManga.source date_added = this@BackupManga.dateAdded viewer_flags = ( this@BackupManga.viewer_flags diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt index a0022e5729..3c4763dbfc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt @@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.data.database.models import android.content.Context import dev.icerock.moko.resources.StringResource import eu.kanade.tachiyomi.ui.library.LibrarySort +import java.io.Serializable import yokai.i18n.MR import yokai.util.lang.getString -import java.io.Serializable interface Category : Serializable { @@ -56,7 +56,21 @@ interface Category : Serializable { fun mangaOrderToString(): String = if (mangaSort != null) mangaSort.toString() else mangaOrder.joinToString("/") + // For dynamic categories + fun dynamicHeaderKey(): String { + if (!isDynamic) throw IllegalStateException("This category is not a dynamic category") + + return when { + sourceId != null -> "${name}$sourceSplitter${sourceId}" + langId != null -> "${langId}$langSplitter${name}" + else -> name + } + } + companion object { + const val sourceSplitter = "◘•◘" + const val langSplitter = "⨼⨦⨠" + var lastCategoriesAddedTo = emptySet() fun create(name: String): Category = CategoryImpl().apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt index 6e685036ca..de3e30df4e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/CategoryImpl.kt @@ -32,10 +32,14 @@ class CategoryImpl : Category { val category = other as Category + if (isDynamic && category.isDynamic) return dynamicHeaderKey() == category.dynamicHeaderKey() + return name == category.name } override fun hashCode(): Int { + if (isDynamic) return dynamicHeaderKey().hashCode() + return name.hashCode() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt index e017568209..7631836fcf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/LibraryManga.kt @@ -1,10 +1,10 @@ package eu.kanade.tachiyomi.data.database.models -import eu.kanade.tachiyomi.ui.library.LibraryItem +import eu.kanade.tachiyomi.domain.manga.models.Manga import kotlin.math.roundToInt -import yokai.data.updateStrategyAdapter data class LibraryManga( + val manga: Manga, var unread: Int = 0, var read: Int = 0, var category: Int = 0, @@ -13,41 +13,11 @@ data class LibraryManga( var latestUpdate: Long = 0, var lastRead: Long = 0, var lastFetch: Long = 0, -) : MangaImpl() { - - var realMangaCount = 0 - get() = if (isBlank()) field else throw IllegalStateException("realMangaCount is only accessible by placeholders") - set(value) { - if (!isBlank()) throw IllegalStateException("realMangaCount can only be set by placeholders") - field = value - } - +) { val hasRead get() = read > 0 - @Transient - var items: List? = null - get() = if (isHidden()) field else throw IllegalStateException("items only accessible by placeholders") - set(value) { - if (!isHidden()) throw IllegalStateException("items can only be set by placeholders") - field = value - } - companion object { - fun createBlank(categoryId: Int): LibraryManga = LibraryManga().apply { - title = "" - id = Long.MIN_VALUE - category = categoryId - } - - fun createHide(categoryId: Int, title: String, hiddenItems: List): LibraryManga = - createBlank(categoryId).apply { - this.title = title - this.status = -1 - this.read = hiddenItems.size - this.items = hiddenItems - } - fun mapper( // manga id: Long, @@ -78,34 +48,37 @@ data class LibraryManga( latestUpdate: Long, lastRead: Long, lastFetch: Long, - ): LibraryManga = createBlank(categoryId.toInt()).apply { - this.id = id - this.source = source - this.url = url - this.artist = artist - this.author = author - this.description = description - this.genre = genre - this.title = title - this.status = status.toInt() - this.thumbnail_url = thumbnailUrl - this.favorite = favorite - this.last_update = lastUpdate ?: 0L - this.initialized = initialized - this.viewer_flags = viewerFlags.toInt() - this.hide_title = hideTitle - this.chapter_flags = chapterFlags.toInt() - this.date_added = dateAdded ?: 0L - this.filtered_scanlators = filteredScanlators - this.update_strategy = updateStrategy.let(updateStrategyAdapter::decode) - this.cover_last_modified = coverLastModified - this.read = readCount.roundToInt() - this.unread = maxOf((total - readCount).roundToInt(), 0) - this.totalChapters = total.toInt() - this.bookmarkCount = bookmarkCount.roundToInt() - this.latestUpdate = latestUpdate - this.lastRead = lastRead - this.lastFetch = lastFetch - } + ): LibraryManga = LibraryManga( + manga = Manga.mapper( + id = id, + source = source, + url = url, + artist = artist, + author = author, + description = description, + genre = genre, + title = title, + status = status, + thumbnailUrl = thumbnailUrl, + favorite = favorite, + lastUpdate = lastUpdate, + initialized = initialized, + viewerFlags = viewerFlags, + hideTitle = hideTitle, + chapterFlags = chapterFlags, + dateAdded = dateAdded, + filteredScanlators = filteredScanlators, + updateStrategy = updateStrategy, + coverLastModified = coverLastModified, + ), + read = readCount.roundToInt(), + unread = maxOf((total - readCount).roundToInt(), 0), + totalChapters = total.toInt(), + bookmarkCount = bookmarkCount.roundToInt(), + category = categoryId.toInt(), + latestUpdate = latestUpdate, + lastRead = lastRead, + lastFetch = lastFetch, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt index fb560da49c..42a0c038eb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt @@ -182,15 +182,13 @@ var Manga.vibrantCoverColor: Int? id?.let { MangaCoverMetadata.setVibrantColor(it, value) } } -fun Manga.Companion.create(source: Long) = MangaImpl().apply { - this.source = source -} - -fun Manga.Companion.create(pathUrl: String, title: String, source: Long = 0) = MangaImpl().apply { - url = pathUrl - this.title = title - this.source = source -} +fun Manga.Companion.create(url: String, title: String, source: Long = 0) = + MangaImpl( + source = source, + url = url, + ).apply { + this.title = title + } fun Manga.Companion.mapper( id: Long, @@ -213,14 +211,12 @@ fun Manga.Companion.mapper( filteredScanlators: String?, updateStrategy: Long, coverLastModified: Long, -) = create(source).apply { +) = create(url, title, source).apply { this.id = id - this.url = url this.artist = artist this.author = author this.description = description this.genre = genre - this.title = title this.status = status.toInt() this.thumbnail_url = thumbnailUrl this.favorite = favorite diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt index afe855bff3..ff0677e9c6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaChapterHistory.kt @@ -12,7 +12,11 @@ import eu.kanade.tachiyomi.domain.manga.models.Manga data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History, var extraChapters: List = emptyList()) { companion object { - fun createBlank() = MangaChapterHistory(MangaImpl(), ChapterImpl(), HistoryImpl()) + fun createBlank() = MangaChapterHistory( + MangaImpl(null, -1, ""), + ChapterImpl(), + HistoryImpl(), + ) fun mapper( // manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt index df0342a5d0..6046ddab15 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt @@ -8,13 +8,11 @@ import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.UpdateStrategy import uy.kohesive.injekt.injectLazy -open class MangaImpl : Manga { - - override var id: Long? = null - - override var source: Long = -1 - - override lateinit var url: String +open class MangaImpl( + override var id: Long? = null, + override var source: Long = -1, + override var url: String = "", +) : Manga { private val customMangaManager: CustomMangaManager by injectLazy() @@ -107,7 +105,7 @@ open class MangaImpl : Manga { } override fun hashCode(): Int { - return if (::url.isInitialized) { + return if (url.isNotBlank()) { url.hashCode() } else { (id ?: 0L).hashCode() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt index cdcf00262a..8f686664a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt @@ -4,6 +4,7 @@ import android.content.Context import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.data.database.models.MangaImpl import eu.kanade.tachiyomi.domain.manga.models.Manga +import java.nio.charset.StandardCharsets import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest @@ -26,7 +27,6 @@ import yokai.domain.library.custom.interactor.GetCustomManga import yokai.domain.library.custom.interactor.RelinkCustomManga import yokai.domain.library.custom.model.CustomMangaInfo import yokai.domain.library.custom.model.CustomMangaInfo.Companion.getMangaInfo -import java.nio.charset.StandardCharsets class CustomMangaManager(val context: Context) { private val scope = CoroutineScope(Dispatchers.IO) @@ -176,8 +176,7 @@ class CustomMangaManager(val context: Context) { val status: Int? = null, ) { - fun toManga() = MangaImpl().apply { - id = this@MangaJson.id + fun toManga() = MangaImpl(id = this.id).apply { title = this@MangaJson.title ?: "" author = this@MangaJson.author artist = this@MangaJson.artist @@ -272,9 +271,6 @@ class CustomMangaManager(val context: Context) { } } - private fun mangaFromComicInfoObject(id: Long, comicInfo: ComicInfo) = MangaImpl().apply { - this.id = id - this.copyFromComicInfo(comicInfo) - this.title = comicInfo.series?.value ?: "" - } + private fun mangaFromComicInfoObject(id: Long, comicInfo: ComicInfo) = + MangaImpl(id = id).apply { this.copyFromComicInfo(comicInfo) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index b2e8eb44c1..105d61c6a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -173,16 +173,17 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val mangaList = ( if (savedMangasList != null) { - val mangas = getLibraryManga.await().filter { - it.id in savedMangasList - }.distinctBy { it.id } + val mangas = + getLibraryManga.await() + .filter { it.manga.id in savedMangasList } + .distinctBy { it.manga.id } val categoryId = inputData.getInt(KEY_CATEGORY, -1) if (categoryId > -1) categoryIds.add(categoryId) mangas } else { getMangaToUpdate() } - ).sortedBy { it.title } + ).sortedBy { it.manga.title } return withIOContext { try { @@ -227,7 +228,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet private suspend fun updateChaptersJob(mangaToAdd: List) { // Initialize the variables holding the progress of the updates. mangaToUpdate.addAll(mangaToAdd) - mangaToUpdateMap.putAll(mangaToAdd.groupBy { it.source }) + mangaToUpdateMap.putAll(mangaToAdd.groupBy { it.manga.source }) checkIfMassiveUpdate() coroutineScope { val list = mangaToUpdateMap.keys.map { source -> @@ -257,42 +258,42 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet private suspend fun updateDetails(mangaToUpdate: List) = coroutineScope { // Initialize the variables holding the progress of the updates. val count = AtomicInteger(0) - val asyncList = mangaToUpdate.groupBy { it.source }.values.map { list -> + val asyncList = mangaToUpdate.groupBy { it.manga.source }.values.map { list -> async { requestSemaphore.withPermit { list.forEach { manga -> ensureActive() - val source = sourceManager.get(manga.source) as? HttpSource ?: return@async + val source = sourceManager.get(manga.manga.source) as? HttpSource ?: return@async notifier.showProgressNotification( - manga, + manga.manga, count.andIncrement, mangaToUpdate.size, ) ensureActive() val networkManga = try { - source.getMangaDetails(manga.copy()) + source.getMangaDetails(manga.manga.copy()) } catch (e: java.lang.Exception) { Logger.e(e) null } if (networkManga != null) { - manga.prepareCoverUpdate(coverCache, networkManga, false) - val thumbnailUrl = manga.thumbnail_url - manga.copyFrom(networkManga) - manga.initialized = true + manga.manga.prepareCoverUpdate(coverCache, networkManga, false) + val thumbnailUrl = manga.manga.thumbnail_url + manga.manga.copyFrom(networkManga) + manga.manga.initialized = true val request: ImageRequest = - if (thumbnailUrl != manga.thumbnail_url) { + if (thumbnailUrl != manga.manga.thumbnail_url) { // load new covers in background - ImageRequest.Builder(context).data(manga.cover()) + ImageRequest.Builder(context).data(manga.manga.cover()) .memoryCachePolicy(CachePolicy.DISABLED).build() } else { - ImageRequest.Builder(context).data(manga.cover()) + ImageRequest.Builder(context).data(manga.manga.cover()) .memoryCachePolicy(CachePolicy.DISABLED) .diskCachePolicy(CachePolicy.WRITE_ONLY) .build() } context.imageLoader.execute(request) - updateManga.await(manga.toMangaUpdate()) + updateManga.await(manga.manga.toMangaUpdate()) } } } @@ -313,9 +314,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val loggedServices = trackManager.services.filter { it.isLogged } mangaToUpdate.forEach { manga -> - notifier.showProgressNotification(manga, count++, mangaToUpdate.size) + notifier.showProgressNotification(manga.manga, count++, mangaToUpdate.size) - val tracks = getTrack.awaitAllByMangaId(manga.id!!) + val tracks = getTrack.awaitAllByMangaId(manga.manga.id!!) tracks.forEach { track -> val service = trackManager.getService(track.sync_id) @@ -324,7 +325,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val newTrack = service.refresh(track) insertTrack.await(newTrack) - syncChaptersWithTrackServiceTwoWay(getChapter.awaitAll(manga.id!!, false), track, service) + syncChaptersWithTrackServiceTwoWay(getChapter.awaitAll(manga.manga.id!!, false), track, service) } catch (e: Exception) { Logger.e(e) } @@ -376,7 +377,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet private fun checkIfMassiveUpdate() { val largestSourceSize = mangaToUpdate - .groupBy { it.source } + .groupBy { it.manga.source } .filterKeys { sourceManager.get(it) !is UnmeteredSource } .maxOfOrNull { it.value.size } ?: 0 if (largestSourceSize > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) { @@ -391,7 +392,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val httpSource = sourceManager.get(source) as? HttpSource ?: return false while (count < mangaToUpdateMap[source]!!.size) { val manga = mangaToUpdateMap[source]!![count] - val shouldDownload = manga.shouldDownloadNewChapters(preferences) + val shouldDownload = manga.manga.shouldDownloadNewChapters(preferences) if (updateMangaChapters(manga, this.count.andIncrement, httpSource, shouldDownload)) { hasDownloads = true } @@ -410,15 +411,15 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet try { var hasDownloads = false ensureActive() - notifier.showProgressNotification(manga, progress, mangaToUpdate.size) - val fetchedChapters = source.getChapterList(manga.copy()) + notifier.showProgressNotification(manga.manga, progress, mangaToUpdate.size) + val fetchedChapters = source.getChapterList(manga.manga.copy()) if (fetchedChapters.isNotEmpty()) { - val newChapters = syncChaptersWithSource(fetchedChapters, manga, source) + val newChapters = syncChaptersWithSource(fetchedChapters, manga.manga, source) if (newChapters.first.isNotEmpty()) { if (shouldDownload) { downloadChapters( - manga, + manga.manga, newChapters.first.sortedBy { it.chapter_number }, ) hasDownloads = true @@ -428,24 +429,24 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet } if (deleteRemoved && newChapters.second.isNotEmpty()) { val removedChapters = newChapters.second.filter { - downloadManager.isChapterDownloaded(it, manga) && + downloadManager.isChapterDownloaded(it, manga.manga) && newChapters.first.none { newChapter -> newChapter.chapter_number == it.chapter_number && it.scanlator.isNullOrBlank() } } if (removedChapters.isNotEmpty()) { - downloadManager.deleteChapters(removedChapters, manga, source) + downloadManager.deleteChapters(removedChapters, manga.manga, source) } } if (newChapters.first.size + newChapters.second.size > 0) { - sendUpdate(manga.id) + sendUpdate(manga.manga.id) } } return@coroutineScope hasDownloads } catch (e: Exception) { if (e !is CancellationException) { - failedUpdates[manga] = e.message - Logger.e { "Failed updating: ${manga.title}: $e" } + failedUpdates[manga.manga] = e.message + Logger.e { "Failed updating: ${manga.manga.title}: $e" } } return@coroutineScope false } @@ -461,17 +462,17 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val restrictions = preferences.libraryUpdateMangaRestriction().get() return mangaToAdd.filter { manga -> when { - MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED -> { - skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_completed) + MANGA_NON_COMPLETED in restrictions && manga.manga.status == SManga.COMPLETED -> { + skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_completed) } MANGA_HAS_UNREAD in restrictions && manga.unread != 0 -> { - skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_not_caught_up) + skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_not_caught_up) } MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasRead -> { - skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_not_started) + skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_not_started) } - manga.update_strategy != UpdateStrategy.ALWAYS_UPDATE -> { - skippedUpdates[manga] = context.getString(MR.strings.skipped_reason_not_always_update) + manga.manga.update_strategy != UpdateStrategy.ALWAYS_UPDATE -> { + skippedUpdates[manga.manga] = context.getString(MR.strings.skipped_reason_not_always_update) } else -> { return@filter true @@ -503,10 +504,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet preferences.libraryUpdateCategories().get().map(String::toInt) if (categoriesToUpdate.isNotEmpty()) { categoryIds.addAll(categoriesToUpdate) - libraryManga.filter { it.category in categoriesToUpdate }.distinctBy { it.id } + libraryManga.filter { it.category in categoriesToUpdate }.distinctBy { it.manga.id } } else { categoryIds.addAll(getCategories.await().mapNotNull { it.id } + 0) - libraryManga.distinctBy { it.id } + libraryManga.distinctBy { it.manga.id } } } @@ -564,13 +565,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet } private fun addMangaToQueue(categoryId: Int, manga: List) { - val mangas = filterMangaToUpdate(manga).sortedBy { it.title } + val mangas = filterMangaToUpdate(manga).sortedBy { it.manga.title } categoryIds.add(categoryId) addManga(mangas) } private fun addCategory(categoryId: Int) { - val mangas = filterMangaToUpdate(runBlocking { getMangaToUpdate(categoryId) }).sortedBy { it.title } + val mangas = filterMangaToUpdate(runBlocking { getMangaToUpdate(categoryId) }).sortedBy { it.manga.title } categoryIds.add(categoryId) addManga(mangas) } @@ -579,7 +580,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet val distinctManga = mangaToAdd.filter { it !in mangaToUpdate } mangaToUpdate.addAll(distinctManga) checkIfMassiveUpdate() - distinctManga.groupBy { it.source }.forEach { + distinctManga.groupBy { it.manga.source }.forEach { // if added queue items is a new source not in the async list or an async list has // finished running if (mangaToUpdateMap[it.key].isNullOrEmpty()) { @@ -727,9 +728,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet if (mangaToUse != null) { builder.putLongArray( KEY_MANGAS, - mangaToUse.firstOrNull()?.id?.let { longArrayOf(it) } ?: longArrayOf(), + mangaToUse.firstOrNull()?.manga?.id?.let { longArrayOf(it) } ?: longArrayOf(), ) - extraManga = mangaToUse.subList(1, mangaToUse.size).mapNotNull { it.id } + extraManga = mangaToUse.subList(1, mangaToUse.size).mapNotNull { it.manga.id } } } val inputData = builder.build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index fd878892e7..323361883f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -185,14 +185,14 @@ class LibraryUpdateNotifier(private val context: Context) { val manga = it.key val chapters = it.value val chapterNames = chapters.map { chapter -> - chapter.preferredChapterName(context, manga, preferences) + chapter.preferredChapterName(context, manga.manga, preferences) } notifications.add( Pair( context.notification(Notifications.CHANNEL_NEW_CHAPTERS) { setSmallIcon(R.drawable.ic_yokai) try { - val request = ImageRequest.Builder(context).data(manga.cover()) + val request = ImageRequest.Builder(context).data(manga.manga.cover()) .networkCachePolicy(CachePolicy.DISABLED) .diskCachePolicy(CachePolicy.ENABLED) .transformations(CircleCropTransformation()) @@ -205,7 +205,7 @@ class LibraryUpdateNotifier(private val context: Context) { } catch (_: Exception) { } setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) - setContentTitle(manga.title) + setContentTitle(manga.manga.title) color = ContextCompat.getColor(context, R.color.secondaryTachiyomi) val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) { "${chapterNames.take(MAX_CHAPTERS - 1).joinToString(", ")}, " + @@ -224,7 +224,7 @@ class LibraryUpdateNotifier(private val context: Context) { setContentIntent( NotificationReceiver.openChapterPendingActivity( context, - manga, + manga.manga, chapters.first(), ), ) @@ -233,7 +233,7 @@ class LibraryUpdateNotifier(private val context: Context) { context.getString(MR.strings.mark_as_read), NotificationReceiver.markAsReadPendingBroadcast( context, - manga, + manga.manga, chapters, Notifications.ID_NEW_CHAPTERS, ), @@ -243,13 +243,13 @@ class LibraryUpdateNotifier(private val context: Context) { context.getString(MR.strings.view_chapters), NotificationReceiver.openChapterPendingActivity( context, - manga, + manga.manga, Notifications.ID_NEW_CHAPTERS, ), ) setAutoCancel(true) }, - manga.id.hashCode(), + manga.manga.id.hashCode(), ), ) } @@ -281,13 +281,13 @@ class LibraryUpdateNotifier(private val context: Context) { NotificationCompat.BigTextStyle() .bigText( updates.keys.joinToString("\n") { - it.title.chop(45) + it.manga.title.chop(45) }, ), ) } } else if (!preferences.hideNotificationContent().get()) { - setContentText(updates.keys.first().title.chop(45)) + setContentText(updates.keys.first().manga.title.chop(45)) } priority = NotificationCompat.PRIORITY_HIGH setGroup(Notifications.GROUP_NEW_CHAPTERS) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index 271d9d1755..db4390ece6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -13,15 +13,15 @@ import eu.kanade.tachiyomi.util.lang.removeArticles import eu.kanade.tachiyomi.util.system.isLTR import eu.kanade.tachiyomi.util.system.timeSpanFromNow import eu.kanade.tachiyomi.util.system.withDefContext +import java.util.* import kotlinx.coroutines.runBlocking import uy.kohesive.injekt.injectLazy -import yokai.domain.ui.UiPreferences -import yokai.i18n.MR -import yokai.util.lang.getString -import java.util.* import yokai.domain.category.interactor.GetCategories import yokai.domain.chapter.interactor.GetChapter import yokai.domain.history.interactor.GetHistory +import yokai.domain.ui.UiPreferences +import yokai.i18n.MR +import yokai.util.lang.getString /** * Adapter storing a list of manga in a certain category. @@ -117,8 +117,8 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : */ fun indexOf(manga: Manga): Int { return currentItems.indexOfFirst { - if (it is LibraryItem) { - it.manga.id == manga.id + if (it is LibraryMangaItem) { + it.manga.manga.id == manga.id } else { false } @@ -142,7 +142,7 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : */ fun allIndexOf(manga: Manga): List { return currentItems.mapIndexedNotNull { index, it -> - if (it is LibraryItem && it.manga.id == manga.id) { + if (it is LibraryMangaItem && it.manga.manga.id == manga.id) { index } else { null @@ -164,7 +164,7 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : } else { val filteredManga = withDefContext { mangas.filter { it.filter(s) } } if (filteredManga.isEmpty() && controller?.presenter?.showAllCategories == false) { - val catId = mangas.firstOrNull()?.let { it.header?.catId ?: it.manga.category } + val catId = (mangas.firstOrNull() as? LibraryMangaItem)?.let { it.header?.catId ?: it.manga.category } val blankItem = catId?.let { controller.presenter.blankItem(it) } updateDataSet(blankItem ?: emptyList()) } else { @@ -202,18 +202,19 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : vibrateOnCategoryChange(item.category.name) item.category.name } - is LibraryItem -> { - val text = if (item.manga.isBlank()) { - return item.header?.category?.name.orEmpty() - } else { + is LibraryPlaceholderItem -> { + item.header?.category?.name.orEmpty() + } + is LibraryMangaItem -> { + val text = when (getSort(position)) { LibrarySort.DragAndDrop -> { - if (item.header.category.isDynamic && item.manga.id != null) { + if (item.header.category.isDynamic && item.manga.manga.id != null) { // FIXME: Don't do blocking - val category = runBlocking { getCategories.awaitByMangaId(item.manga.id!!) }.firstOrNull()?.name + val category = runBlocking { getCategories.awaitByMangaId(item.manga.manga.id!!) }.firstOrNull()?.name category ?: context.getString(MR.strings.default_value) } else { - val title = item.manga.title + val title = item.manga.manga.title if (preferences.removeArticles().get()) { title.removeArticles().chop(15) } else { @@ -222,14 +223,14 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : } } LibrarySort.DateFetched -> { - val id = item.manga.id ?: return "" + val id = item.manga.manga.id ?: return "" // FIXME: Don't do blocking val history = runBlocking { getChapter.awaitAll(id, false) } val last = history.maxOfOrNull { it.date_fetch } context.timeSpanFromNow(MR.strings.fetched_, last ?: 0) } LibrarySort.LastRead -> { - val id = item.manga.id ?: return "" + val id = item.manga.manga.id ?: return "" // FIXME: Don't do blocking val history = runBlocking { getHistory.awaitAllByMangaId(id) } val last = history.maxOfOrNull { it.last_read } @@ -256,21 +257,20 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : } } LibrarySort.LatestChapter -> { - context.timeSpanFromNow(MR.strings.updated_, item.manga.last_update) + context.timeSpanFromNow(MR.strings.updated_, item.manga.manga.last_update) } LibrarySort.DateAdded -> { - context.timeSpanFromNow(MR.strings.added_, item.manga.date_added) + context.timeSpanFromNow(MR.strings.added_, item.manga.manga.date_added) } LibrarySort.Title -> { val title = if (preferences.removeArticles().get()) { - item.manga.title.removeArticles() + item.manga.manga.title.removeArticles() } else { - item.manga.title + item.manga.manga.title } getFirstLetter(title) } } - } if (!isSingleCategory) { vibrateOnCategoryChange(item.header?.category?.name.orEmpty()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 393fe3244d..9f49ec22f5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -451,7 +451,7 @@ open class LibraryController( private fun setActiveCategory() { val currentCategory = presenter.categories.indexOfFirst { - if (presenter.showAllCategories) it.order == activeCategory else presenter.currentCategory == it.id + if (presenter.showAllCategories) it.order == activeCategory else presenter.currentCategoryId == it.id } if (currentCategory > -1) { binding.categoryRecycler.setCategories(currentCategory) @@ -521,14 +521,13 @@ open class LibraryController( } private fun openRandomManga(global: Boolean) { - val items = if (global) { - presenter.allLibraryItems - } else { - adapter.currentItems - }.filter { (it is LibraryItem && !it.manga.isBlank() && !it.manga.isHidden() && (!it.manga.initialized || it.manga.unread > 0)) } + val items = + if (global) { presenter.currentLibraryItems } else { adapter.currentItems } + .filterIsInstance() + .filter { !it.manga.manga.initialized || it.manga.unread > 0 } if (items.isNotEmpty()) { - val item = items.random() as LibraryItem - openManga(item.manga) + val item = items.random() as LibraryMangaItem + openManga(item.manga.manga) } } @@ -662,7 +661,7 @@ open class LibraryController( createActionModeIfNeeded() } - if (presenter.libraryItems.isNotEmpty() && !isSubClass) { + if (presenter.libraryItemsToDisplay.isNotEmpty() && !isSubClass) { presenter.restoreLibrary() if (justStarted) { val activityBinding = activityBinding ?: return @@ -706,7 +705,7 @@ open class LibraryController( if (!LibraryUpdateJob.isRunning(context)) { when { !presenter.showAllCategories && presenter.groupType == BY_DEFAULT -> { - presenter.findCurrentCategory()?.let { + presenter.currentCategory?.let { updateLibrary(it) } } @@ -904,7 +903,7 @@ open class LibraryController( } } else { val newOffset = - presenter.categories.indexOfFirst { presenter.currentCategory == it.id } + + presenter.categories.indexOfFirst { presenter.currentCategoryId == it.id } + (if (next) 1 else -1) if (if (!next) { newOffset > -1 @@ -1013,7 +1012,7 @@ open class LibraryController( override fun getSpanSize(position: Int): Int { if (libraryLayout == LibraryItem.LAYOUT_LIST) return managerSpanCount val item = this@LibraryController.mAdapter?.getItem(position) - return if (item is LibraryHeaderItem || item is SearchGlobalItem || (item is LibraryItem && item.manga.isBlank())) { + return if (item is LibraryHeaderItem || item is SearchGlobalItem || item is LibraryPlaceholderItem) { managerSpanCount } else { 1 @@ -1459,7 +1458,7 @@ open class LibraryController( adapter.removeAllScrollableHeaders() } adapter.setFilter(query) - if (presenter.allLibraryItems.isEmpty()) return true + if (presenter.currentLibraryItems.isEmpty()) return true viewScope.launchUI { adapter.performFilterAsync() } @@ -1478,7 +1477,6 @@ open class LibraryController( } private fun setSelection(manga: Manga, selected: Boolean) { - if (manga.isBlank()) return val currentMode = adapter.mode if (selected) { if (selectedMangas.add(manga)) { @@ -1528,7 +1526,7 @@ open class LibraryController( toggleSelection(position) return } - val manga = (adapter.getItem(position) as? LibraryItem)?.manga ?: return + val manga = (adapter.getItem(position) as? LibraryMangaItem)?.manga?.manga ?: return val activity = activity ?: return val chapter = presenter.getFirstUnread(manga) ?: return activity.apply { @@ -1544,9 +1542,8 @@ open class LibraryController( } private fun toggleSelection(position: Int) { - val item = adapter.getItem(position) as? LibraryItem ?: return - if (item.manga.isBlank()) return - setSelection(item.manga, !adapter.isSelected(position)) + val item = adapter.getItem(position) as? LibraryMangaItem ?: return + setSelection(item.manga.manga, !adapter.isSelected(position)) invalidateActionMode() } @@ -1562,14 +1559,14 @@ open class LibraryController( * @return true if the item should be selected, false otherwise. */ override fun onItemClick(view: View?, position: Int): Boolean { - val item = adapter.getItem(position) as? LibraryItem ?: return false + val item = adapter.getItem(position) as? LibraryMangaItem ?: return false return if (adapter.mode == SelectableAdapter.Mode.MULTI) { snack?.dismiss() lastClickPosition = position toggleSelection(position) false } else { - openManga(item.manga) + openManga(item.manga.manga) false } } @@ -1591,10 +1588,10 @@ open class LibraryController( */ override fun onItemLongClick(position: Int) { val item = adapter.getItem(position) - if (item !is LibraryItem) return + if (item !is LibraryMangaItem) return snack?.dismiss() if (libraryLayout == LibraryItem.LAYOUT_COVER_ONLY_GRID && actionMode == null) { - snack = view?.snack(item.manga.title) { + snack = view?.snack(item.manga.manga.title) { anchorView = activityBinding?.bottomNav view.elevation = 15f.dpToPx } @@ -1648,9 +1645,9 @@ open class LibraryController( } private fun setSelection(position: Int, selected: Boolean = true) { - val item = adapter.getItem(position) as? LibraryItem ?: return + val item = adapter.getItem(position) as? LibraryMangaItem ?: return - setSelection(item.manga, selected) + setSelection(item.manga.manga, selected) invalidateActionMode() } @@ -1674,7 +1671,7 @@ open class LibraryController( override fun shouldMoveItem(fromPosition: Int, toPosition: Int): Boolean { if (adapter.isSelected(fromPosition)) toggleSelection(fromPosition) - val item = adapter.getItem(fromPosition) as? LibraryItem ?: return false + val item = adapter.getItem(fromPosition) as? LibraryMangaItem ?: return false val newHeader = adapter.getSectionHeader(toPosition) as? LibraryHeaderItem if (toPosition < 1) return false return (adapter.getItem(toPosition) !is LibraryHeaderItem) && ( @@ -1696,11 +1693,11 @@ open class LibraryController( destroyActionModeIfNeeded() // if nothing moved if (lastItemPosition == null) return - val item = adapter.getItem(position) as? LibraryItem ?: return + val item = adapter.getItem(position) as? LibraryMangaItem ?: return val newHeader = adapter.getSectionHeader(position) as? LibraryHeaderItem val libraryItems = getSectionItems(adapter.getSectionHeader(position), item) - .filterIsInstance() - val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id } + .filterIsInstance() + val mangaIds = libraryItems.mapNotNull { (it as? LibraryMangaItem)?.manga?.manga?.id } if (newHeader?.category?.id == item.manga.category) { presenter.rearrangeCategory(item.manga.category, mangaIds) } else { @@ -1832,8 +1829,8 @@ open class LibraryController( if (category?.isDynamic == false && sortBy == LibrarySort.DragAndDrop.categoryValue) { val item = adapter.findCategoryHeader(catId) ?: return val libraryItems = adapter.getSectionItems(item) - .filterIsInstance() - val mangaIds = libraryItems.mapNotNull { (it as? LibraryItem)?.manga?.id } + .filterIsInstance() + val mangaIds = libraryItems.mapNotNull { (it as? LibraryMangaItem)?.manga?.manga?.id } presenter.rearrangeCategory(catId, mangaIds) } else { presenter.sortCategory(catId, sortBy) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt index abbcdc7224..86287493a5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt @@ -64,23 +64,24 @@ class LibraryGridHolder( * @param item the manga item to bind. */ override fun onSetValues(item: LibraryItem) { + if (item !is LibraryMangaItem) throw IllegalStateException("Only LibraryMangaItem can use grid holder") // Update the title and subtitle of the manga. setCards(adapter.showOutline, binding.card, binding.unreadDownloadBadge.root) binding.playButton.transitionName = "library chapter $bindingAdapterPosition transition" - binding.constraintLayout.isVisible = !item.manga.isBlank() - binding.title.text = item.manga.title.highlightText(item.filter, color) - binding.behindTitle.text = item.manga.title - val mangaColor = item.manga.dominantCoverColors + binding.constraintLayout.isVisible = item.manga.manga.id != Long.MIN_VALUE + binding.title.text = item.manga.manga.title.highlightText(item.filter, color) + binding.behindTitle.text = item.manga.manga.title + val mangaColor = item.manga.manga.dominantCoverColors binding.coverConstraint.backgroundColor = mangaColor?.first ?: itemView.context.getResourceColor(R.attr.background) binding.behindTitle.setTextColor( mangaColor?.second ?: itemView.context.getResourceColor(R.attr.colorOnBackground), ) - val authorArtist = if (item.manga.author == item.manga.artist || item.manga.artist.isNullOrBlank()) { - item.manga.author?.trim() ?: "" + val authorArtist = if (item.manga.manga.author == item.manga.manga.artist || item.manga.manga.artist.isNullOrBlank()) { + item.manga.manga.author?.trim() ?: "" } else { listOfNotNull( - item.manga.author?.trim()?.takeIf { it.isNotBlank() }, - item.manga.artist?.trim()?.takeIf { it.isNotBlank() }, + item.manga.manga.author?.trim()?.takeIf { it.isNotBlank() }, + item.manga.manga.artist?.trim()?.takeIf { it.isNotBlank() }, ).joinToString(", ") } binding.subtitle.text = authorArtist.highlightText(item.filter, color) @@ -101,7 +102,7 @@ class LibraryGridHolder( // Update the cover. binding.coverThumbnail.dispose() - setCover(item.manga) + setCover(item.manga.manga) } override fun toggleActivation() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt index 73e10f7fd9..5760a4808f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt @@ -5,9 +5,6 @@ import androidx.core.graphics.ColorUtils import androidx.core.view.isVisible import com.google.android.material.card.MaterialCardView import eu.kanade.tachiyomi.R -import yokai.i18n.MR -import yokai.util.lang.getString -import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.system.getResourceColor @@ -17,9 +14,7 @@ import eu.kanade.tachiyomi.util.view.setCards * Generic class used to hold the displayed data of a manga in the library. * @param view the inflated view for this holder. * @param adapter the adapter handling this holder. - * @param listener a listener to react to the single tap and long tap events. */ - abstract class LibraryHolder( view: View, val adapter: LibraryCategoryAdapter, @@ -43,7 +38,7 @@ abstract class LibraryHolder( */ abstract fun onSetValues(item: LibraryItem) - fun setUnreadBadge(badge: LibraryBadge, item: LibraryItem) { + fun setUnreadBadge(badge: LibraryBadge, item: LibraryMangaItem) { val showTotal = item.header.category.sortingMode() == LibrarySort.TotalChapters badge.setUnreadDownload( when { @@ -54,7 +49,7 @@ abstract class LibraryHolder( }, when { item.downloadCount == -1 -> -1 - item.manga.isLocal() -> -2 + item.manga.manga.isLocal() -> -2 else -> item.downloadCount }, showTotal, @@ -63,7 +58,7 @@ abstract class LibraryHolder( ) } - fun setReadingButton(item: LibraryItem) { + fun setReadingButton(item: LibraryMangaItem) { itemView.findViewById(R.id.play_layout)?.isVisible = item.manga.unread > 0 && !item.hideReadingButton } @@ -80,8 +75,8 @@ abstract class LibraryHolder( override fun onLongClick(view: View?): Boolean { return if (adapter.isLongPressDragEnabled) { - val manga = (adapter.getItem(flexibleAdapterPosition) as? LibraryItem)?.manga - if (manga != null && !isDraggable && !manga.isBlank() && !manga.isHidden()) { + val manga = (adapter.getItem(flexibleAdapterPosition) as? LibraryMangaItem)?.manga + if (manga != null && !isDraggable) { adapter.mItemLongClickListener.onItemLongClick(flexibleAdapterPosition) toggleActivation() true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index 50f63727e2..134e8241ae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -1,205 +1,48 @@ package eu.kanade.tachiyomi.ui.library import android.content.Context -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.ImageView -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.content.ContextCompat -import androidx.core.view.isVisible -import androidx.core.view.updateLayoutParams +import androidx.annotation.CallSuper import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.StaggeredGridLayoutManager import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.davidea.flexibleadapter.items.IFilterable import eu.davidea.flexibleadapter.items.IFlexible -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.models.LibraryManga -import eu.kanade.tachiyomi.data.database.models.seriesType import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.databinding.MangaGridItemBinding import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.util.system.dpToPx -import eu.kanade.tachiyomi.util.view.compatToolTipText -import eu.kanade.tachiyomi.widget.AutofitRecyclerView import uy.kohesive.injekt.injectLazy import yokai.domain.ui.UiPreferences -class LibraryItem( - val manga: LibraryManga, +abstract class LibraryItem( header: LibraryHeaderItem, - private val context: Context?, + internal val context: Context?, ) : AbstractSectionableItem(header), IFilterable { - var downloadCount = -1 - var unreadType = 2 - var sourceLanguage: String? = null var filter = "" - private val sourceManager: SourceManager by injectLazy() + internal val sourceManager: SourceManager by injectLazy() private val uiPreferences: UiPreferences by injectLazy() private val preferences: PreferencesHelper by injectLazy() - private val uniformSize: Boolean + internal val uniformSize: Boolean get() = uiPreferences.uniformGrid().get() - private val libraryLayout: Int + internal val libraryLayout: Int get() = preferences.libraryLayout().get() val hideReadingButton: Boolean get() = preferences.hideStartReadingButton().get() - override fun getLayoutRes(): Int { - return if (libraryLayout == LAYOUT_LIST || manga.isBlank()) { - R.layout.manga_list_item - } else { - R.layout.manga_grid_item - } - } - - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LibraryHolder { - val parent = adapter.recyclerView - return if (parent is AutofitRecyclerView) { - val libraryLayout = libraryLayout - val isFixedSize = uniformSize - if (libraryLayout == LAYOUT_LIST || manga.isBlank()) { - LibraryListHolder(view, adapter as LibraryCategoryAdapter) - } else { - view.apply { - val isStaggered = parent.layoutManager is StaggeredGridLayoutManager - val binding = MangaGridItemBinding.bind(this) - binding.behindTitle.isVisible = libraryLayout == LAYOUT_COVER_ONLY_GRID - if (libraryLayout >= LAYOUT_COMFORTABLE_GRID) { - binding.textLayout.isVisible = libraryLayout == LAYOUT_COMFORTABLE_GRID - binding.card.setCardForegroundColor( - ContextCompat.getColorStateList( - context, - R.color.library_comfortable_grid_foreground, - ), - ) - } - if (isFixedSize) { - binding.constraintLayout.layoutParams = FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT, - ) - binding.coverThumbnail.maxHeight = Int.MAX_VALUE - binding.coverThumbnail.minimumHeight = 0 - binding.constraintLayout.minHeight = 0 - binding.coverThumbnail.scaleType = ImageView.ScaleType.CENTER_CROP - binding.coverThumbnail.adjustViewBounds = false - binding.coverThumbnail.updateLayoutParams { - height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT - dimensionRatio = "2:3" - } - } - if (libraryLayout != LAYOUT_COMFORTABLE_GRID) { - binding.card.updateLayoutParams { - bottomMargin = (if (isStaggered) 2 else 6).dpToPx - } - } - binding.setBGAndFG(libraryLayout) - } - val gridHolder = LibraryGridHolder( - view, - adapter as LibraryCategoryAdapter, - libraryLayout == LAYOUT_COMPACT_GRID, - isFixedSize, - ) - if (!isFixedSize) { - gridHolder.setFreeformCoverRatio(manga, parent) - } - gridHolder - } - } else { - LibraryListHolder(view, adapter as LibraryCategoryAdapter) - } - } - + @CallSuper override fun bindViewHolder( adapter: FlexibleAdapter>, holder: LibraryHolder, position: Int, payloads: MutableList?, ) { - if (holder is LibraryGridHolder && !holder.fixedSize) { - holder.setFreeformCoverRatio(manga, adapter.recyclerView as? AutofitRecyclerView) - } holder.onSetValues(this) (holder as? LibraryGridHolder)?.setSelected(adapter.isSelected(position)) - val layoutParams = holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams - layoutParams?.isFullSpan = manga.isBlank() - if (libraryLayout == LAYOUT_COVER_ONLY_GRID) { - holder.itemView.compatToolTipText = manga.title - } - } - - /** - * Returns true if this item is draggable. - */ - override fun isDraggable(): Boolean { - return !manga.isBlank() && header.category.isDragAndDrop - } - - override fun isEnabled(): Boolean { - return !manga.isBlank() - } - - override fun isSelectable(): Boolean { - return !manga.isBlank() - } - - /** - * Filters a manga depending on a query. - * - * @param constraint the query to apply. - * @return true if the manga should be included, false otherwise. - */ - override fun filter(constraint: String): Boolean { - filter = constraint - if (manga.isBlank() && manga.title.isBlank()) { - return constraint.isEmpty() - } - val sourceName by lazy { sourceManager.getOrStub(manga.source).name } - return manga.title.contains(constraint, true) || - (manga.author?.contains(constraint, true) ?: false) || - (manga.artist?.contains(constraint, true) ?: false) || - sourceName.contains(constraint, true) || - if (constraint.contains(",")) { - val genres = manga.genre?.split(", ") - constraint.split(",").all { containsGenre(it.trim(), genres) } - } else { - containsGenre(constraint, manga.genre?.split(", ")) - } - } - - private fun containsGenre(tag: String, genres: List?): Boolean { - if (tag.trim().isEmpty()) return true - context ?: return false - val seriesType by lazy { manga.seriesType(context, sourceManager) } - return if (tag.startsWith("-")) { - val realTag = tag.substringAfter("-") - genres?.find { - it.trim().equals(realTag, ignoreCase = true) || seriesType.equals(realTag, true) - } == null - } else { - genres?.find { - it.trim().equals(tag, ignoreCase = true) || seriesType.equals(tag, true) - } != null - } - } - - override fun equals(other: Any?): Boolean { - if (other is LibraryItem) { - return manga.id == other.manga.id && manga.category == other.manga.category - } - return false - } - - override fun hashCode(): Int { - return 31 * manga.id!!.hashCode() + header!!.hashCode() + (holder.itemView.layoutParams as? StaggeredGridLayoutManager.LayoutParams)?.isFullSpan = this is LibraryPlaceholderItem } companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt index 38d17e926c..e3696c68e7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -39,22 +39,25 @@ class LibraryListHolder( setCards(adapter.showOutline, binding.card, binding.unreadDownloadBadge.root) binding.title.isVisible = true binding.constraintLayout.minHeight = 56.dpToPx - if (item.manga.isBlank()) { + if (item is LibraryPlaceholderItem) { binding.constraintLayout.minHeight = 0 binding.constraintLayout.updateLayoutParams { height = ViewGroup.MarginLayoutParams.WRAP_CONTENT } - if (item.manga.status == -1) { - binding.title.text = null - binding.title.isVisible = false - } else { - binding.title.text = itemView.context.getString( - if (adapter.hasActiveFilters && item.manga.realMangaCount >= 1) { - MR.strings.no_matches_for_filters_short - } else { - MR.strings.category_is_empty - }, - ) + when (item.type) { + is LibraryPlaceholderItem.Type.Blank -> { + binding.title.text = itemView.context.getString( + if (adapter.hasActiveFilters && item.type.mangaCount >= 1) { + MR.strings.no_matches_for_filters_short + } else { + MR.strings.category_is_empty + }, + ) + } + is LibraryPlaceholderItem.Type.Hidden -> { + binding.title.text = null + binding.title.isVisible = false + } } binding.title.textAlignment = View.TEXT_ALIGNMENT_CENTER binding.card.isVisible = false @@ -63,6 +66,9 @@ class LibraryListHolder( binding.subtitle.isVisible = false return } + + if (item !is LibraryMangaItem) error("${item::class.qualifiedName} is not a valid item") + binding.constraintLayout.updateLayoutParams { height = 52.dpToPx } @@ -71,16 +77,16 @@ class LibraryListHolder( binding.title.textAlignment = View.TEXT_ALIGNMENT_TEXT_START // Update the binding.title of the manga. - binding.title.text = item.manga.title.highlightText(item.filter, color) + binding.title.text = item.manga.manga.title.highlightText(item.filter, color) setUnreadBadge(binding.unreadDownloadBadge.badgeView, item) val authorArtist = - if (item.manga.author == item.manga.artist || item.manga.artist.isNullOrBlank()) { - item.manga.author?.trim() ?: "" + if (item.manga.manga.author == item.manga.manga.artist || item.manga.manga.artist.isNullOrBlank()) { + item.manga.manga.author?.trim() ?: "" } else { listOfNotNull( - item.manga.author?.trim()?.takeIf { it.isNotBlank() }, - item.manga.artist?.trim()?.takeIf { it.isNotBlank() }, + item.manga.manga.author?.trim()?.takeIf { it.isNotBlank() }, + item.manga.manga.artist?.trim()?.takeIf { it.isNotBlank() }, ).joinToString(", ") } @@ -95,7 +101,7 @@ class LibraryListHolder( // Update the cover. binding.coverThumbnail.dispose() - binding.coverThumbnail.loadManga(item.manga) + binding.coverThumbnail.loadManga(item.manga.manga) } override fun onActionStateChanged(position: Int, actionState: Int) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaItem.kt new file mode 100644 index 0000000000..28de0771f0 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryMangaItem.kt @@ -0,0 +1,180 @@ +package eu.kanade.tachiyomi.ui.library + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.StaggeredGridLayoutManager +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.LibraryManga +import eu.kanade.tachiyomi.data.database.models.seriesType +import eu.kanade.tachiyomi.databinding.MangaGridItemBinding +import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.view.compatToolTipText +import eu.kanade.tachiyomi.widget.AutofitRecyclerView + +class LibraryMangaItem( + val manga: LibraryManga, + header: LibraryHeaderItem, + context: Context?, +) : LibraryItem(header, context) { + + var downloadCount = -1 + var unreadType = 2 + var sourceLanguage: String? = null + + override fun getLayoutRes(): Int { + return if (libraryLayout == LAYOUT_LIST) { + R.layout.manga_list_item + } else { + R.layout.manga_grid_item + } + } + + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LibraryHolder { + val listHolder by lazy { LibraryListHolder(view, adapter as LibraryCategoryAdapter) } + val parent = adapter.recyclerView + if (parent !is AutofitRecyclerView) return listHolder + + val libraryLayout = libraryLayout + val isFixedSize = uniformSize + + if (libraryLayout == LAYOUT_LIST) { return listHolder } + + view.apply { + val isStaggered = parent.layoutManager is StaggeredGridLayoutManager + val binding = MangaGridItemBinding.bind(this) + binding.behindTitle.isVisible = libraryLayout == LAYOUT_COVER_ONLY_GRID + if (libraryLayout >= LAYOUT_COMFORTABLE_GRID) { + binding.textLayout.isVisible = libraryLayout == LAYOUT_COMFORTABLE_GRID + binding.card.setCardForegroundColor( + ContextCompat.getColorStateList( + context, + R.color.library_comfortable_grid_foreground, + ), + ) + } + if (isFixedSize) { + binding.constraintLayout.layoutParams = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + ) + binding.coverThumbnail.maxHeight = Int.MAX_VALUE + binding.coverThumbnail.minimumHeight = 0 + binding.constraintLayout.minHeight = 0 + binding.coverThumbnail.scaleType = ImageView.ScaleType.CENTER_CROP + binding.coverThumbnail.adjustViewBounds = false + binding.coverThumbnail.updateLayoutParams { + height = ConstraintLayout.LayoutParams.MATCH_CONSTRAINT + dimensionRatio = "2:3" + } + } + if (libraryLayout != LAYOUT_COMFORTABLE_GRID) { + binding.card.updateLayoutParams { + bottomMargin = (if (isStaggered) 2 else 6).dpToPx + } + } + binding.setBGAndFG(libraryLayout) + } + val gridHolder = LibraryGridHolder( + view, + adapter as LibraryCategoryAdapter, + libraryLayout == LAYOUT_COMPACT_GRID, + isFixedSize, + ) + if (!isFixedSize) { + gridHolder.setFreeformCoverRatio(manga.manga, parent) + } + return gridHolder + } + + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: LibraryHolder, + position: Int, + payloads: MutableList?, + ) { + if (holder is LibraryGridHolder && !holder.fixedSize) { + holder.setFreeformCoverRatio(manga.manga, adapter.recyclerView as? AutofitRecyclerView) + } + super.bindViewHolder(adapter, holder, position, payloads) + if (libraryLayout == LAYOUT_COVER_ONLY_GRID) { + holder.itemView.compatToolTipText = manga.manga.title + } + } + + /** + * Returns true if this item is draggable. + */ + override fun isDraggable(): Boolean { + return header.category.isDragAndDrop + } + + override fun isEnabled(): Boolean { + return true + } + + override fun isSelectable(): Boolean { + return true + } + + /** + * Filters a manga depending on a query. + * + * @param constraint the query to apply. + * @return true if the manga should be included, false otherwise. + */ + override fun filter(constraint: String): Boolean { + filter = constraint + if (manga.manga.title.isBlank()) { + return constraint.isEmpty() + } + val sourceName by lazy { sourceManager.getOrStub(manga.manga.source).name } + return manga.manga.title.contains(constraint, true) || + (manga.manga.author?.contains(constraint, true) ?: false) || + (manga.manga.artist?.contains(constraint, true) ?: false) || + sourceName.contains(constraint, true) || + if (constraint.contains(",")) { + val genres = manga.manga.genre?.split(", ") + constraint.split(",").all { containsGenre(it.trim(), genres) } + } else { + containsGenre(constraint, manga.manga.genre?.split(", ")) + } + } + + private fun containsGenre(tag: String, genres: List?): Boolean { + if (tag.trim().isEmpty()) return true + context ?: return false + + val seriesType by lazy { manga.manga.seriesType(context, sourceManager) } + return if (tag.startsWith("-")) { + val realTag = tag.substringAfter("-") + genres?.find { + it.trim().equals(realTag, ignoreCase = true) || seriesType.equals(realTag, true) + } == null + } else { + genres?.find { + it.trim().equals(tag, ignoreCase = true) || seriesType.equals(tag, true) + } != null + } + } + + override fun equals(other: Any?): Boolean { + if (other is LibraryMangaItem) { + return manga.manga.id == other.manga.manga.id && manga.category == other.manga.category + } + return false + } + + override fun hashCode(): Int { + return 31 * manga.manga.id.hashCode() + header!!.hashCode() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPlaceholderItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPlaceholderItem.kt new file mode 100644 index 0000000000..d0b810a4a6 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPlaceholderItem.kt @@ -0,0 +1,57 @@ +package eu.kanade.tachiyomi.ui.library + +import android.content.Context +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.IFlexible +import eu.kanade.tachiyomi.R + +/** + * Placeholder item to indicate if the category is hidden or empty/filtered out. + */ +class LibraryPlaceholderItem ( + val category: Int, + val type: Type, + header: LibraryHeaderItem, + context: Context?, +) : LibraryItem(header, context) { + + override fun getLayoutRes(): Int = R.layout.manga_list_item + + override fun createViewHolder(view: View, adapter: FlexibleAdapter>): LibraryHolder { + return LibraryListHolder(view, adapter as LibraryCategoryAdapter) + } + + override fun filter(constraint: String): Boolean { + filter = constraint + + if (type !is Type.Hidden || type.title.isBlank()) return constraint.isEmpty() + + return type.title.contains(constraint, true) + } + + override fun equals(other: Any?): Boolean { + if (other is LibraryPlaceholderItem) { + return category == other.category + } + return false + } + + override fun hashCode(): Int { + return 31 * Long.MIN_VALUE.hashCode() + header!!.hashCode() + } + + sealed class Type { + data class Hidden(val title: String, val hiddenItems: List) : Type() + data class Blank(val mangaCount: Int) : Type() + } + + companion object { + fun hidden(category: Int, header: LibraryHeaderItem, context: Context?, title: String, hiddenItems: List) = + LibraryPlaceholderItem(category, Type.Hidden(title, hiddenItems), header, context) + + fun blank(category: Int, header: LibraryHeaderItem, context: Context?, mangaCount: Int = 0) = + LibraryPlaceholderItem(category, Type.Blank(mangaCount), header, context) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index b2d43befea..f1a526adb3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -4,6 +4,8 @@ import eu.kanade.tachiyomi.core.preference.minusAssign import eu.kanade.tachiyomi.core.preference.plusAssign import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.models.Category +import eu.kanade.tachiyomi.data.database.models.Category.Companion.langSplitter +import eu.kanade.tachiyomi.data.database.models.Category.Companion.sourceSplitter import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter.Companion.copy import eu.kanade.tachiyomi.data.database.models.LibraryManga @@ -57,7 +59,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.retry -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext @@ -81,6 +82,9 @@ import yokai.i18n.MR import yokai.util.isLewd import yokai.util.lang.getString +typealias LibraryMap = Map> +typealias LibraryMutableMap = MutableMap> + /** * Presenter of [LibraryController]. */ @@ -120,20 +124,27 @@ class LibraryPresenter( var categories: List = emptyList() private set - private var removeArticles: Boolean = preferences.removeArticles().get() - /** All categories of the library, in case they are hidden because of hide categories is on */ private var allCategories: List = emptyList() - /** List of all manga to update the */ - // TODO: Store sectioned before flattening it out for "show all categories" - private var currentLibrary: Map> = mapOf() - var libraryItems: List = emptyList() - private var sectionedLibraryItems: MutableMap> = mutableMapOf() - var currentCategory = -1 + private var removeArticles: Boolean = preferences.removeArticles().get() + + /** List of all manga */ + var currentLibrary: LibraryMap = mapOf() private set - var allLibraryItems: List = emptyList() + val currentLibraryItems: List + get() = currentLibrary.values.flatten() + /** List of all manga to be displayed */ + private var libraryToDisplay: LibraryMutableMap = mutableMapOf() + val libraryItemsToDisplay: List + get() = libraryToDisplay.values.flatten() + + var currentCategoryId = -1 private set + var currentCategory: Category? + get() = allCategories.find { it.id == currentCategoryId } + set(value) { currentCategoryId = value?.id ?: 0 } + private var hiddenLibraryItems: List = emptyList() var forceShowAllCategories = false val showAllCategories @@ -170,16 +181,14 @@ class LibraryPresenter( fun isCategoryMoreThanOne(): Boolean = allCategories.size > 1 - fun findCurrentCategory() = allCategories.find { it.id == currentCategory } - /** Save the current list to speed up loading later */ override fun onDestroy() { val isSubController = controllerIsSubClass super.onDestroy() if (!isSubController) { - lastLibraryItems = libraryItems + lastDisplayedLibrary = libraryToDisplay lastCategories = categories - lastAllLibraryItems = allLibraryItems + lastLibrary = currentLibrary } } @@ -187,12 +196,12 @@ class LibraryPresenter( super.onCreate() if (!controllerIsSubClass) { - lastLibraryItems?.let { libraryItems = it } + lastDisplayedLibrary?.let { libraryToDisplay = it } lastCategories?.let { categories = it } - lastAllLibraryItems?.let { allLibraryItems = it } + lastLibrary?.let { currentLibrary = it } lastCategories = null - lastLibraryItems = null - lastAllLibraryItems = null + lastDisplayedLibrary = null + lastLibrary = null } subscribeLibrary() @@ -212,12 +221,16 @@ class LibraryPresenter( } fun getItemCountInCategories(categoryId: Int): Int { - val items = sectionedLibraryItems[categoryId] - return if (items?.firstOrNull()?.manga?.isHidden() == true || items?.firstOrNull()?.manga?.isBlank() == true) { - items.firstOrNull()?.manga?.read ?: 0 - } else { - sectionedLibraryItems[categoryId]?.size ?: 0 + val category = categories.find { it.id == categoryId } + val items = libraryToDisplay[category] + val firstItem = items?.firstOrNull() as? LibraryPlaceholderItem? + if (firstItem != null) { + if (firstItem.type !is LibraryPlaceholderItem.Type.Hidden) { + return 0 + } + return firstItem.type.hiddenItems.size } + return items?.size ?: 0 } private fun subscribeLibrary() { @@ -239,21 +252,23 @@ class LibraryPresenter( allCategories = data.allCategories val library = data.items - val hiddenItems = library.filter { it.manga.isHidden() }.mapNotNull { it.manga.items }.flatten() + val hiddenItems = data.hiddenItems - setDownloadCount(library) - setUnreadBadge(library) - setSourceLanguage(library) + library.forEach { (_, items) -> + setDownloadCount(items) + setUnreadBadge(items) + setSourceLanguage(items) + } setDownloadCount(hiddenItems) setUnreadBadge(hiddenItems) setSourceLanguage(hiddenItems) - allLibraryItems = library + currentLibrary = library hiddenLibraryItems = hiddenItems val mangaMap = library .applyFilters() .applySort() - val freshStart = libraryItems.isEmpty() + val freshStart = libraryToDisplay.isEmpty() sectionLibrary(mangaMap, freshStart) } } @@ -269,16 +284,16 @@ class LibraryPresenter( fun switchSection(order: Int) { preferences.lastUsedCategory().set(order) - val category = categories.find { it.order == order }?.id ?: return + val category = categories.find { it.order == order } ?: return currentCategory = category - view?.onNextLibraryUpdate(sectionedLibraryItems[currentCategory] ?: blankItem()) + view?.onNextLibraryUpdate(libraryToDisplay[category] ?: blankItem()) } - fun blankItem(id: Int = currentCategory, categories: List? = null): List { + fun blankItem(id: Int = currentCategoryId, categories: List? = null): List { val actualCategories = categories ?: this.categories return listOf( - LibraryItem( - LibraryManga.createBlank(id), + LibraryPlaceholderItem.blank( + id, LibraryHeaderItem({ actualCategories.getOrDefault(id) }, id), viewContext, ), @@ -286,20 +301,17 @@ class LibraryPresenter( } fun restoreLibrary() { - val items = libraryItems val show = showAllCategories || !libraryIsGrouped || categories.size == 1 - sectionedLibraryItems = items.groupBy { it.header.category.id!! }.toMutableMap() - if (!show && currentCategory == -1) { - currentCategory = categories.find { - it.order == preferences.lastUsedCategory().get() - }?.id ?: 0 + if (!show && currentCategoryId == -1) { + currentCategory = categories.find { it.order == preferences.lastUsedCategory().get() } } view?.onNextLibraryUpdate( if (!show) { - sectionedLibraryItems[currentCategory] - ?: sectionedLibraryItems[categories.first().id] ?: blankItem() + libraryToDisplay[currentCategory] + ?: libraryToDisplay[categories.first()] + ?: blankItem() } else { - libraryItems + libraryItemsToDisplay }, true, ) @@ -307,25 +319,29 @@ class LibraryPresenter( fun getMangaInCategories(catId: Int?): List? { catId ?: return null - return allLibraryItems.filter { it.header.category.id == catId }.map { it.manga } + return currentLibraryItems + .filterIsInstance() + .filter { it.header.category.id == catId } + .map { it.manga } } - private suspend fun sectionLibrary(items: List, freshStart: Boolean = false) { - libraryItems = items + private suspend fun sectionLibrary(items: LibraryMap, freshStart: Boolean = false) { val showAll = showAllCategories || !libraryIsGrouped || categories.size <= 1 - sectionedLibraryItems = items.groupBy { it.header.category.id ?: 0 }.toMutableMap() - if (!showAll && currentCategory == -1) { - currentCategory = categories.find { - it.order == preferences.lastUsedCategory().get() - }?.id ?: 0 + + libraryToDisplay = items.toMutableMap() + + if (!showAll && currentCategoryId == -1) { + currentCategory = categories.find { it.order == preferences.lastUsedCategory().get() } } + withUIContext { view?.onNextLibraryUpdate( if (!showAll) { - sectionedLibraryItems[currentCategory] - ?: sectionedLibraryItems[categories.first().id] ?: blankItem() + libraryToDisplay[currentCategory] + ?: libraryToDisplay[categories.first()] + ?: blankItem() } else { - libraryItems + libraryItemsToDisplay }, freshStart, ) @@ -337,7 +353,7 @@ class LibraryPresenter( * * @param items the items to filter. */ - private suspend fun List.applyFilters(): List { + private suspend fun LibraryMap.applyFilters(): LibraryMap { val filterPrefs = getPreferencesFlow().first() val showEmptyCategoriesWhileFiltering = preferences.showEmptyCategoriesWhileFiltering().get() @@ -353,57 +369,68 @@ class LibraryPresenter( filterPrefs.filterContentType == 0 ) hasActiveFilters = !filtersOff - val missingCategorySet = categories.mapNotNull { it.id }.toMutableSet() val realCount = mutableMapOf() - val filteredItems = this.filter f@{ item -> + val filteredItems = this.mapValues { (key, items) -> if (showEmptyCategoriesWhileFiltering) { - realCount[item.manga.category] = sectionedLibraryItems[item.manga.category]?.size ?: 0 + realCount[key.id ?: 0] = libraryToDisplay[key]?.size ?: 0 } - if (!showEmptyCategoriesWhileFiltering && item.manga.isHidden()) { - val subItems = sectionedLibraryItems[item.manga.category]?.takeUnless { it.size <= 1 } - ?: hiddenLibraryItems.filter { it.manga.category == item.manga.category } - if (subItems.isEmpty()) { - return@f filtersOff - } else { - return@f subItems.any { - matchesFilters( - it, - filterPrefs, - filterTrackers, - ) + items.filter f@{ item -> + if (item is LibraryMangaItem) { + return@f matchesFilters( + item, + filterPrefs, + filterTrackers, + ) + } + + if ( + !showEmptyCategoriesWhileFiltering + && item is LibraryPlaceholderItem + && item.type is LibraryPlaceholderItem.Type.Hidden + ) { + val subItems = (libraryToDisplay[key] ?: hiddenLibraryItems) + .filterIsInstance() + .filter { it.manga.category == item.category } + if (subItems.isEmpty()) { + return@f filtersOff + } else { + return@f subItems.any { + matchesFilters( + it, + filterPrefs, + filterTrackers, + ) + } } } - } else if (item.manga.isBlank() || item.manga.isHidden()) { - missingCategorySet.remove(item.manga.category) - return@f if (showAllCategories) { + + if (showAllCategories) { filtersOff || showEmptyCategoriesWhileFiltering } else { true } + }.ifEmpty { + if (showEmptyCategoriesWhileFiltering) { + val catId = key.id!! + listOf( + LibraryPlaceholderItem.blank( + catId, + LibraryHeaderItem({ this@LibraryPresenter.categories.getOrDefault(catId) }, catId), + viewContext, + realCount[catId] ?: 0, + ), + ) + } else { + emptyList() + } } - val matches = matchesFilters( - item, - filterPrefs, - filterTrackers, - ) - if (matches) { - missingCategorySet.remove(item.manga.category) - } - matches - }.toMutableList() - if (showEmptyCategoriesWhileFiltering) { - missingCategorySet.forEach { - filteredItems.add( - blankItem(it).first().apply { manga.realMangaCount = realCount[it] ?: 0 } - ) - } - } + }.toMutableMap() return filteredItems } private suspend fun matchesFilters( - item: LibraryItem, + item: LibraryMangaItem, filterPrefs: ItemPreferences, filterTrackers: String, ): Boolean { @@ -423,9 +450,9 @@ class LibraryPresenter( if (filterPrefs.filterMangaType > 0) { if (if (filterPrefs.filterMangaType == Manga.TYPE_MANHWA) { - item.manga.seriesType(sourceManager = sourceManager) !in arrayOf(filterPrefs.filterMangaType, Manga.TYPE_WEBTOON) + item.manga.manga.seriesType(sourceManager = sourceManager) !in arrayOf(filterPrefs.filterMangaType, Manga.TYPE_WEBTOON) } else { - filterPrefs.filterMangaType != item.manga.seriesType(sourceManager = sourceManager) + filterPrefs.filterMangaType != item.manga.manga.seriesType(sourceManager = sourceManager) } ) { return false @@ -433,51 +460,51 @@ class LibraryPresenter( } // Filter for completed status of manga - if (filterPrefs.filterCompleted == STATE_INCLUDE && item.manga.status != SManga.COMPLETED) return false - if (filterPrefs.filterCompleted == STATE_EXCLUDE && item.manga.status == SManga.COMPLETED) return false + if (filterPrefs.filterCompleted == STATE_INCLUDE && item.manga.manga.status != SManga.COMPLETED) return false + if (filterPrefs.filterCompleted == STATE_EXCLUDE && item.manga.manga.status == SManga.COMPLETED) return false if (!matchesFilterTracking(item, filterPrefs.filterTracked, filterTrackers)) return false // Filter for downloaded manga if (filterPrefs.filterDownloaded != STATE_IGNORE) { val isDownloaded = when { - item.manga.isLocal() -> true + item.manga.manga.isLocal() -> true item.downloadCount != -1 -> item.downloadCount > 0 - else -> downloadManager.getDownloadCount(item.manga) > 0 + else -> downloadManager.getDownloadCount(item.manga.manga) > 0 } return if (filterPrefs.filterDownloaded == STATE_INCLUDE) isDownloaded else !isDownloaded } // Filter for NSFW/SFW contents - if (filterPrefs.filterContentType == STATE_INCLUDE) return !item.manga.isLewd() - if (filterPrefs.filterContentType == STATE_EXCLUDE) return item.manga.isLewd() + if (filterPrefs.filterContentType == STATE_INCLUDE) return !item.manga.manga.isLewd() + if (filterPrefs.filterContentType == STATE_EXCLUDE) return item.manga.manga.isLewd() return true } private suspend fun matchesCustomFilters( - item: LibraryItem, + item: LibraryMangaItem, customFilters: FilteredLibraryController, filterTrackers: String, ): Boolean { val statuses = customFilters.filterStatus if (statuses.isNotEmpty()) { - if (item.manga.status !in statuses) return false + if (item.manga.manga.status !in statuses) return false } val seriesTypes = customFilters.filterMangaType if (seriesTypes.isNotEmpty()) { - if (item.manga.seriesType(sourceManager = sourceManager) !in seriesTypes) return false + if (item.manga.manga.seriesType(sourceManager = sourceManager) !in seriesTypes) return false } val languages = customFilters.filterLanguages if (languages.isNotEmpty()) { - if (getLanguage(item.manga) !in languages) return false + if (getLanguage(item.manga.manga) !in languages) return false } val sources = customFilters.filterSources if (sources.isNotEmpty()) { - if (item.manga.source !in sources) return false + if (item.manga.manga.source !in sources) return false } val trackingScore = customFilters.filterTrackingScore if (trackingScore > 0 || trackingScore == -1) { - val tracks = getTrack.awaitAllByMangaId(item.manga.id!!) + val tracks = getTrack.awaitAllByMangaId(item.manga.manga.id!!) val hasTrack = loggedServices.any { service -> tracks.any { it.sync_id == service.id } @@ -502,7 +529,7 @@ class LibraryPresenter( } val tags = customFilters.filterTags if (tags.isNotEmpty()) { - val genres = item.manga.getGenres() ?: return false + val genres = item.manga.manga.getGenres() ?: return false if (tags.none { tag -> genres.any { it.equals(tag, true) } }) return false } return true @@ -518,8 +545,8 @@ class LibraryPresenter( } private suspend fun LibraryManga.getStartYear(): Int { - if (getChapter.awaitAll(id!!, false).any { it.read }) { - val chapters = getHistory.awaitAllByMangaId(id!!).filter { it.last_read > 0 } + if (getChapter.awaitAll(manga.id!!, false).any { it.read }) { + val chapters = getHistory.awaitAllByMangaId(manga.id!!).filter { it.last_read > 0 } val date = chapters.minOfOrNull { it.last_read } ?: return -1 val cal = Calendar.getInstance().apply { timeInMillis = date } return if (date <= 0L) -1 else cal.get(Calendar.YEAR) @@ -536,13 +563,13 @@ class LibraryPresenter( } private suspend fun matchesFilterTracking( - item: LibraryItem, + item: LibraryMangaItem, filterTracked: Int, filterTrackers: String, ): Boolean { // Filter for tracked (or per tracked service) if (filterTracked != STATE_IGNORE) { - val tracks = getTrack.awaitAllByMangaId(item.manga.id!!) + val tracks = getTrack.awaitAllByMangaId(item.manga.manga.id!!) val hasTrack = loggedServices.any { service -> tracks.any { it.sync_id == service.id } @@ -585,19 +612,22 @@ class LibraryPresenter( if (!preferences.downloadBadge().get()) { // Unset download count if the preference is not enabled. for (item in itemList) { + if (item !is LibraryMangaItem) continue item.downloadCount = -1 } return } for (item in itemList) { - item.downloadCount = downloadManager.getDownloadCount(item.manga) + if (item !is LibraryMangaItem) continue + item.downloadCount = downloadManager.getDownloadCount(item.manga.manga) } } private fun setUnreadBadge(itemList: List) { val unreadType = preferences.unreadBadgeType().get() for (item in itemList) { + if (item !is LibraryMangaItem) continue item.unreadType = unreadType } } @@ -605,7 +635,8 @@ class LibraryPresenter( private fun setSourceLanguage(itemList: List) { val showLanguageBadges = preferences.languageBadge().get() for (item in itemList) { - item.sourceLanguage = if (showLanguageBadges) getLanguage(item.manga) else null + if (item !is LibraryMangaItem) continue + item.sourceLanguage = if (showLanguageBadges) getLanguage(item.manga.manga) else null } } @@ -626,88 +657,99 @@ class LibraryPresenter( * * @param itemList the map to sort. */ - private fun List.applySort(): List { + private fun LibraryMap.applySort(): LibraryMap { val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 -> - if (i1.header.category.id == i2.header.category.id) { - val category = i1.header.category - if (category.mangaOrder.isEmpty() && category.mangaSort == null) { - category.changeSortTo(preferences.librarySortingMode().get()) - if (category.id == 0) { - preferences.defaultMangaOrder() - .set(category.mangaSort.toString()) - } else if (!category.isDynamic) { - onCategoryUpdate( - CategoryUpdate( - id = category.id!!.toLong(), - mangaOrder = category.mangaOrderToString(), - ) - ) - } - } - val compare = when { - category.mangaSort != null -> { - var sort = when (category.sortingMode() ?: LibrarySort.Title) { - LibrarySort.Title -> sortAlphabetical(i1, i2) - LibrarySort.LatestChapter -> i2.manga.latestUpdate.compareTo(i1.manga.latestUpdate) - LibrarySort.Unread -> when { - i1.manga.unread == i2.manga.unread -> 0 - i1.manga.unread == 0 -> if (category.isAscending()) 1 else -1 - i2.manga.unread == 0 -> if (category.isAscending()) -1 else 1 - else -> i1.manga.unread.compareTo(i2.manga.unread) - } - LibrarySort.LastRead -> { - i1.manga.lastRead.compareTo(i2.manga.lastRead) - } - LibrarySort.TotalChapters -> { - i1.manga.totalChapters.compareTo(i2.manga.totalChapters) - } - LibrarySort.DateFetched -> { - i1.manga.lastFetch.compareTo(i2.manga.lastFetch) - } - LibrarySort.DateAdded -> i2.manga.date_added.compareTo(i1.manga.date_added) - LibrarySort.DragAndDrop -> { - if (category.isDynamic) { - val category1 = - allCategories.find { i1.manga.category == it.id }?.order - ?: 0 - val category2 = - allCategories.find { i2.manga.category == it.id }?.order - ?: 0 - category1.compareTo(category2) - } else { - sortAlphabetical(i1, i2) - } + val category = i1.header.category + val compare = when { + i1 is LibraryPlaceholderItem -> -1 + i2 is LibraryPlaceholderItem -> 1 + i1 !is LibraryMangaItem || i2 !is LibraryMangaItem -> 0 + category.mangaSort != null -> { + var sort = when (category.sortingMode() ?: LibrarySort.Title) { + LibrarySort.Title -> sortAlphabetical(i1, i2) + LibrarySort.LatestChapter -> i2.manga.latestUpdate.compareTo(i1.manga.latestUpdate) + LibrarySort.Unread -> when { + i1.manga.unread == i2.manga.unread -> 0 + i1.manga.unread == 0 -> if (category.isAscending()) 1 else -1 + i2.manga.unread == 0 -> if (category.isAscending()) -1 else 1 + else -> i1.manga.unread.compareTo(i2.manga.unread) + } + LibrarySort.LastRead -> { + i1.manga.lastRead.compareTo(i2.manga.lastRead) + } + LibrarySort.TotalChapters -> { + i1.manga.totalChapters.compareTo(i2.manga.totalChapters) + } + LibrarySort.DateFetched -> { + i1.manga.lastFetch.compareTo(i2.manga.lastFetch) + } + LibrarySort.DateAdded -> i2.manga.manga.date_added.compareTo(i1.manga.manga.date_added) + LibrarySort.DragAndDrop -> { + if (category.isDynamic) { + val category1 = + allCategories.find { i1.manga.category == it.id }?.order + ?: 0 + val category2 = + allCategories.find { i2.manga.category == it.id }?.order + ?: 0 + category1.compareTo(category2) + } else { + sortAlphabetical(i1, i2) } } - if (!category.isAscending()) sort *= -1 - sort } - category.mangaOrder.isNotEmpty() -> { - val order = category.mangaOrder - val index1 = order.indexOf(i1.manga.id!!) - val index2 = order.indexOf(i2.manga.id!!) - when { - index1 == index2 -> 0 - index1 == -1 -> -1 - index2 == -1 -> 1 - else -> index1.compareTo(index2) - } + if (!category.isAscending()) sort *= -1 + sort + } + category.mangaOrder.isNotEmpty() -> { + val order = category.mangaOrder + val index1 = order.indexOf(i1.manga.manga.id!!) + val index2 = order.indexOf(i2.manga.manga.id!!) + when { + index1 == index2 -> 0 + index1 == -1 -> -1 + index2 == -1 -> 1 + else -> index1.compareTo(index2) } - else -> 0 - } - if (compare == 0) { - sortAlphabetical(i1, i2) - } else { - compare } + else -> 0 + } + if (compare == 0 && i1 is LibraryMangaItem && i2 is LibraryMangaItem) { + sortAlphabetical(i1, i2) } else { - val category = i1.header.category.order - val category2 = i2.header.category.order - category.compareTo(category2) + compare } } - return this.sortedWith(Comparator(sortFn)) + return this.mapValues { (category, values) -> + // Making sure category has valid sort + if (category.mangaOrder.isEmpty() && category.mangaSort == null) { + category.changeSortTo(preferences.librarySortingMode().get()) + if (category.id == 0) { + preferences.defaultMangaOrder() + .set(category.mangaSort.toString()) + } else if (!category.isDynamic) { + onCategoryUpdate( + CategoryUpdate( + id = category.id!!.toLong(), + mangaOrder = category.mangaOrderToString(), + ) + ) + } + } + + values.sortedWith(Comparator(sortFn)) + }.toSortedMap { category, category2 -> + // Force default category to already be at the top. This also for some reason fixed a bug where Default + // category would disappear whenever a new category is added. + if (category.id == 0) { + -1 + } else if (category2.id == 0) { + 1 + } else { + category.order.compareTo(category2.order) + } + } } /** Gets the category by id @@ -726,11 +768,11 @@ class LibraryPresenter( * @param i1 the first manga * @param i2 the second manga to compare */ - private fun sortAlphabetical(i1: LibraryItem, i2: LibraryItem): Int { + private fun sortAlphabetical(i1: LibraryMangaItem, i2: LibraryMangaItem): Int { return if (removeArticles) { - i1.manga.title.removeArticles().compareTo(i2.manga.title.removeArticles(), true) + i1.manga.manga.title.removeArticles().compareTo(i2.manga.manga.title.removeArticles(), true) } else { - i1.manga.title.compareTo(i2.manga.title, true) + i1.manga.manga.title.compareTo(i2.manga.manga.title, true) } } @@ -769,33 +811,33 @@ class LibraryPresenter( ) } - private fun MutableList.addRemovedManga( - removedManga: Map>, - ): MutableList { - removedManga.keys.forEach { key -> - val manga = removedManga[key] ?: return@forEach - val headerItem = try { - manga.first().header - } catch (e: NoSuchElementException) { - return@forEach // No hidden manga to be handled - } - val mergedTitle = manga.joinToString("-") { - it.manga.title + "-" + it.manga.author - } - this.add( - LibraryItem( - LibraryManga.createHide( - headerItem.catId, - mergedTitle, - manga, - ), - headerItem, - viewContext, - ), - ) - } - return this - } +// private fun MutableList.addRemovedManga( +// removedManga: Map>, +// ): MutableList { +// removedManga.keys.forEach { key -> +// val manga = removedManga[key] ?: return@forEach +// val headerItem = try { +// manga.first().header +// } catch (e: NoSuchElementException) { +// return@forEach // No hidden manga to be handled +// } +// val mergedTitle = manga.joinToString("-") { +// it.manga.title + "-" + it.manga.manga.author +// } +// this.add( +// LibraryItem( +// LibraryManga.createHide( +// headerItem.catId, +// mergedTitle, +// manga, +// ), +// headerItem, +// viewContext, +// ), +// ) +// } +// return this +// } /** * Library's flow. @@ -854,15 +896,15 @@ class LibraryPresenter( } private fun getLibraryItems( - allCategories: List, + dbCategories: List, libraryManga: List, sortingMode: Int, isAscending: Boolean, showAll: Boolean, collapsedCategories: Set, defaultCategory: Category, - ): Triple, List, List> { - val categories = allCategories.toMutableList() + ): Triple, List> { + val categories = dbCategories.mapNotNull { if (it.id == null) null else it }.toMutableList() val hiddenItems = mutableListOf() val categoryAll = Category.createAll( @@ -871,88 +913,81 @@ class LibraryPresenter( isAscending, ) val catItemAll = LibraryHeaderItem({ categoryAll }, -1) - val categorySet = mutableSetOf() + + // NOTE: Don't call header.category, only header.catId val headerItems = ( - categories.mapNotNull { category -> - val id = category.id - if (id == null) { - null - } else { - id to LibraryHeaderItem({ categories.getOrDefault(id) }, id) - } - } + (-1 to catItemAll) + (0 to LibraryHeaderItem({ categories.getOrDefault(0) }, 0)) + categories.map { category -> + val id = category.id!! + id to LibraryHeaderItem({ this@LibraryPresenter.categories.getOrDefault(id) }, id) + } + (0 to LibraryHeaderItem({ this@LibraryPresenter.categories.getOrDefault(0) }, 0)) ).toMap() - // TODO: - - // val map = libraryManga.groupBy { - // categories.getOrDefault(it.category) - // } - - val items = if (libraryIsGrouped) { - libraryManga - } else { - libraryManga.distinctBy { it.id } - }.mapNotNull { - val headerItem = ( - if (!libraryIsGrouped) { - catItemAll - } else { - headerItems[it.category] - } - ) ?: return@mapNotNull null - categorySet.add(it.category) - LibraryItem(it, headerItem, viewContext) - }.toMutableList() - val categoriesHidden = if (forceShowAllCategories || controllerIsSubClass) { emptySet() } else { collapsedCategories.mapNotNull { it.toIntOrNull() }.toSet() } - if (categorySet.contains(0)) categories.add(0, defaultCategory) - if (libraryIsGrouped) { - categories.forEach { category -> - val catId = category.id ?: return@forEach - if (catId > 0 && !categorySet.contains(catId) && (catId !in categoriesHidden || !showAll)) { - val headerItem = headerItems[catId] - if (headerItem != null) { - items.add( - LibraryItem(LibraryManga.createBlank(catId), headerItem, viewContext), + val map = if (!libraryIsGrouped) + libraryManga + .asSequence() + .distinctBy { it.manga.id } + .map { LibraryMangaItem(it, catItemAll, viewContext) } + .groupBy { categoryAll } + else { + val rt = libraryManga + .asSequence() + .mapNotNull { + val headerItem = headerItems[it.category] ?: return@mapNotNull null + LibraryMangaItem(it, headerItem, viewContext) + } + .groupBy { it.header.catId } + + // Only show default category when needed + if (rt.containsKey(0)) categories.add(0, defaultCategory) + + // NOTE: Empty list means hide the category entirely + categories + .associateWith { rt[it.id].orEmpty() } + .mapValues { (key, values) -> + val catId = key.id!! // null check already handled by mapNotNull + val headerItem = headerItems[catId]!! // null check already handled by mapNotNull + + // Hide category if "Show all categories" is enabled and there's more than 1 category + if (catId in categoriesHidden && showAll && categories.size > 1) { + val mergedTitle = values.joinToString("-") { + it.manga.manga.title + "-" + it.manga.manga.author + } + libraryToDisplay[key] = values + hiddenItems.addAll(values) + return@mapValues listOf( + LibraryPlaceholderItem.hidden( + catId, + headerItem, + viewContext, + mergedTitle, + values, + ), ) } - } else if (catId in categoriesHidden && showAll && categories.size > 1) { - val mangaToRemove = items.filter { it.manga.category == catId } - val mergedTitle = mangaToRemove.joinToString("-") { - it.manga.title + "-" + it.manga.author - } - sectionedLibraryItems[catId] = mangaToRemove - hiddenItems.addAll(mangaToRemove) - items.removeAll(mangaToRemove) - val headerItem = headerItems[catId] - if (headerItem != null) { - items.add( - LibraryItem( - LibraryManga.createHide( - catId, - mergedTitle, - mangaToRemove, - ), + + // Making sure empty category is shown properly + values.ifEmpty { + listOf( + LibraryPlaceholderItem.blank( + catId, headerItem, viewContext, ), ) } } - } - } + }.toMutableMap() - categories.forEach { - it.isHidden = it.id in categoriesHidden && showAll && categories.size > 1 - } + categories.forEach { it.isHidden = it.id in categoriesHidden && showAll && categories.size > 1 } return Triple( - items, + map, if (!libraryIsGrouped) { arrayListOf(categoryAll) } else { @@ -968,12 +1003,13 @@ class LibraryPresenter( isAscending: Boolean, groupType: Int, collapsedDynamicCategories: Set, - ): Triple, List, List> { + ): Triple, List> { val tagItems: MutableMap = mutableMapOf() + val hiddenItems = mutableListOf() // internal function to make headers fun makeOrGetHeader(name: String, checkNameSwap: Boolean = false): LibraryHeaderItem { - tagItems.get(name)?.let { return it } + tagItems[name]?.let { return it } if (checkNameSwap && name.contains(" ")) { val swappedName = name.split(" ").reversed().joinToString(" ") if (tagItems.containsKey(swappedName)) { @@ -985,26 +1021,32 @@ class LibraryPresenter( return headerItem } + val hiddenDynamics = if (controllerIsSubClass) { + emptySet() + } else { + collapsedDynamicCategories + } + val unknown = context.getString(MR.strings.unknown) - val items = libraryManga.distinctBy { it.id }.map { manga -> + val items = libraryManga.distinctBy { it.manga.id }.map { manga -> when (groupType) { BY_TAG -> { - val tags = if (manga.genre.isNullOrBlank()) { + val tags = if (manga.manga.genre.isNullOrBlank()) { listOf(unknown) } else { - manga.genre?.split(",")?.mapNotNull { + manga.manga.genre?.split(",")?.mapNotNull { val tag = it.trim().capitalizeWords() tag.ifBlank { null } } ?: listOf(unknown) } tags.map { - LibraryItem(manga, makeOrGetHeader(it), viewContext) + LibraryMangaItem(manga, makeOrGetHeader(it), viewContext) } } BY_TRACK_STATUS -> { - val tracks = getTrack.awaitAllByMangaId(manga.id!!) + val tracks = getTrack.awaitAllByMangaId(manga.manga.id!!) val track = tracks.find { track -> - loggedServices.any { it.id == track?.sync_id } + loggedServices.any { it.id == track.sync_id } } val service = loggedServices.find { it.id == track?.sync_id } val status: String = if (track != null && service != null) { @@ -1016,12 +1058,12 @@ class LibraryPresenter( } else { view?.view?.context?.getString(MR.strings.not_tracked) ?: "" } - listOf(LibraryItem(manga, makeOrGetHeader(status), viewContext)) + listOf(LibraryMangaItem(manga, makeOrGetHeader(status), viewContext)) } BY_SOURCE -> { - val source = sourceManager.getOrStub(manga.source) + val source = sourceManager.getOrStub(manga.manga.source) listOf( - LibraryItem( + LibraryMangaItem( manga, makeOrGetHeader("${source.name}$sourceSplitter${source.id}"), viewContext, @@ -1029,26 +1071,26 @@ class LibraryPresenter( ) } BY_AUTHOR -> { - if (manga.artist.isNullOrBlank() && manga.author.isNullOrBlank()) { - listOf(LibraryItem(manga, makeOrGetHeader(unknown), viewContext)) + if (manga.manga.artist.isNullOrBlank() && manga.manga.author.isNullOrBlank()) { + listOf(LibraryMangaItem(manga, makeOrGetHeader(unknown), viewContext)) } else { listOfNotNull( - manga.author.takeUnless { it.isNullOrBlank() }, - manga.artist.takeUnless { it.isNullOrBlank() }, + manga.manga.author.takeUnless { it.isNullOrBlank() }, + manga.manga.artist.takeUnless { it.isNullOrBlank() }, ).map { it.split(",", "/", " x ", " - ", ignoreCase = true).mapNotNull { name -> val author = name.trim() author.ifBlank { null } } }.flatten().distinct().map { - LibraryItem(manga, makeOrGetHeader(it, true), viewContext) + LibraryMangaItem(manga, makeOrGetHeader(it, true), viewContext) } } } BY_LANGUAGE -> { - val lang = getLanguage(manga) + val lang = getLanguage(manga.manga) listOf( - LibraryItem( + LibraryMangaItem( manga, makeOrGetHeader( lang?.plus(langSplitter)?.plus( @@ -1063,15 +1105,11 @@ class LibraryPresenter( ), ) } - else -> listOf(LibraryItem(manga, makeOrGetHeader(context.mapStatus(manga.status)), viewContext)) // BY_STATUS + // BY_STATUS + else -> listOf(LibraryMangaItem(manga, makeOrGetHeader(context.mapStatus(manga.manga.status)), viewContext)) } - }.flatten().toMutableList() + }.flatten().groupBy { it.header.catId } - val hiddenDynamics = if (controllerIsSubClass) { - emptySet() - } else { - collapsedDynamicCategories - } val headers = tagItems.map { item -> Category.createCustom( item.key, @@ -1102,37 +1140,35 @@ class LibraryPresenter( if (!preferences.collapsedDynamicAtBottom().get()) return@let headers headers.filterNot { it.isHidden } + headers.filter { it.isHidden } } - headers.forEach { category -> - val catId = category.id ?: return@forEach - val headerItem = - tagItems[ - when { - category.sourceId != null -> "${category.name}$sourceSplitter${category.sourceId}" - category.langId != null -> "${category.langId}$langSplitter${category.name}" - else -> category.name - }, - ] - if (category.isHidden) { - val mangaToRemove = items.filter { it.header.catId == catId } - val mergedTitle = mangaToRemove.joinToString("-") { - it.manga.title + "-" + it.manga.author - } - sectionedLibraryItems[catId] = mangaToRemove - items.removeAll { it.header.catId == catId } - if (headerItem != null) { - items.add( - LibraryItem( - LibraryManga.createHide(catId, mergedTitle, mangaToRemove), - headerItem, - viewContext, - ), - ) + + val map = headers + .associateWith { items[it.id].orEmpty() } + .mapValues { (key, values) -> + val catId = key.id!! // null check already handled by mapNotNull + val headerItem = tagItems[key.dynamicHeaderKey()] + if (key.isHidden) { + val mergedTitle = values.joinToString("-") { + it.manga.manga.title + "-" + it.manga.manga.author + } + libraryToDisplay[key] = values + hiddenItems.addAll(values) + if (headerItem != null) { + return@mapValues listOf( + LibraryPlaceholderItem.hidden( + catId, + headerItem, + viewContext, + mergedTitle, + values, + ), + ) + } } + values } - } headers.forEachIndexed { index, category -> category.order = index } - return Triple(items, headers, listOf()) + return Triple(map, headers, hiddenItems) } private fun mapTrackingOrder(status: String): String { @@ -1165,7 +1201,7 @@ class LibraryPresenter( /** Requests the library to be filtered. */ fun requestFilterUpdate() { presenterScope.launch { - val mangaMap = allLibraryItems + val mangaMap = currentLibrary .applyFilters() .applySort() sectionLibrary(mangaMap) @@ -1174,11 +1210,11 @@ class LibraryPresenter( private fun requestBadgeUpdate(badgeUpdate: (List) -> Unit) { presenterScope.launch { - val mangaMap = allLibraryItems - badgeUpdate(mangaMap) - allLibraryItems = mangaMap - val current = libraryItems - badgeUpdate(current) + val mangaMap = currentLibrary + mangaMap.forEach { (_, items) -> badgeUpdate(items) } + currentLibrary = mangaMap + val current = libraryToDisplay + current.forEach { (_, items) -> badgeUpdate(items) } sectionLibrary(current) } } @@ -1201,7 +1237,7 @@ class LibraryPresenter( /** Requests the library to be sorted. */ private fun requestSortUpdate() { presenterScope.launch { - val mangaMap = libraryItems + val mangaMap = libraryToDisplay .applySort() sectionLibrary(mangaMap) } @@ -1338,7 +1374,7 @@ class LibraryPresenter( if (catId == 0) { emptyList() } else { - getCategories.awaitByMangaId(manga.id!!) + getCategories.awaitByMangaId(manga.manga.id!!) .filter { it.id != oldCatId } + listOf(category) } @@ -1346,11 +1382,11 @@ class LibraryPresenter( mc.add(cat.id!!.toLong()) } - setMangaCategories.await(manga.id!!, mc) + setMangaCategories.await(manga.manga.id!!, mc) if (category.mangaSort == null) { val ids = mangaIds.toMutableList() - if (!ids.contains(manga.id!!)) ids.add(manga.id!!) + if (!ids.contains(manga.manga.id!!)) ids.add(manga.manga.id!!) category.mangaOrder = ids if (category.id == 0) { preferences.defaultMangaOrder() @@ -1371,7 +1407,7 @@ class LibraryPresenter( /** Returns if manga is in a category by id */ fun mangaIsInCategory(manga: LibraryManga, catId: Int?): Boolean { // FIXME: Don't do blocking - val categories = runBlocking { getCategories.awaitByMangaId(manga.id!!) }.map { it.id } + val categories = runBlocking { getCategories.awaitByMangaId(manga.manga.id!!) }.map { it.id } return catId in categories } @@ -1511,11 +1547,9 @@ class LibraryPresenter( } companion object { - private var lastLibraryItems: List? = null + private var lastDisplayedLibrary: LibraryMutableMap? = null private var lastCategories: List? = null - private var lastAllLibraryItems: List? = null - private const val sourceSplitter = "◘•◘" - private const val langSplitter = "⨼⨦⨠" + private var lastLibrary: LibraryMap? = null private const val dynamicCategorySplitter = "▄╪\t▄╪\t▄" private val randomTags = arrayOf(0, 1, 2) @@ -1531,9 +1565,9 @@ class LibraryPresenter( private const val randomGroupOfTagsNegate = 2 fun onLowMemory() { - lastLibraryItems = null + lastDisplayedLibrary = null lastCategories = null - lastAllLibraryItems = null + lastLibrary = null } suspend fun setSearchSuggestion( @@ -1553,15 +1587,15 @@ class LibraryPresenter( preferences.librarySearchSuggestion().set( when (val value = random.nextInt(0, 5)) { randomSource -> { - val distinctSources = getLibraryManga.await().distinctBy { it.source } + val distinctSources = getLibraryManga.await().distinctBy { it.manga.source } val randomSource = sourceManager.get( - distinctSources.randomOrNull(random)?.source ?: 0L, + distinctSources.randomOrNull(random)?.manga?.source ?: 0L, )?.name randomSource?.chopByWords(30) } randomTitle -> { - getLibraryManga.await().randomOrNull(random)?.title?.chopByWords(30) + getLibraryManga.await().randomOrNull(random)?.manga?.title?.chopByWords(30) } in randomTags -> { val tags = RecentsPresenter.getRecentManga(true) @@ -1605,11 +1639,11 @@ class LibraryPresenter( ) { val libraryManga = getLibraryManga.await() libraryManga.forEach { manga -> - if (manga.id == null) return@forEach - if (manga.date_added == 0L) { - val chapters = getChapter.awaitAll(manga) - manga.date_added = chapters.minByOrNull { it.date_fetch }?.date_fetch ?: 0L - updateManga.await(MangaUpdate(manga.id!!, dateAdded = manga.date_added)) + if (manga.manga.id == null) return@forEach + if (manga.manga.date_added == 0L) { + val chapters = getChapter.awaitAll(manga.manga.id!!, manga.manga.filtered_scanlators?.isNotBlank() == true) + manga.manga.date_added = chapters.minByOrNull { it.date_fetch }?.date_fetch ?: 0L + updateManga.await(MangaUpdate(manga.manga.id!!, dateAdded = manga.manga.date_added)) } } } @@ -1631,15 +1665,15 @@ class LibraryPresenter( val getLibraryManga: GetLibraryManga by injectLazy() val libraryManga = getLibraryManga.await() libraryManga.forEach { manga -> - if (manga.id == null) return@forEach - if (manga.thumbnail_url?.startsWith("custom", ignoreCase = true) == true) { - val file = cc.getCoverFile(manga.thumbnail_url, !manga.favorite) + if (manga.manga.id == null) return@forEach + if (manga.manga.thumbnail_url?.startsWith("custom", ignoreCase = true) == true) { + val file = cc.getCoverFile(manga.manga.thumbnail_url, !manga.manga.favorite) if (file != null && file.exists()) { - file.renameTo(cc.getCustomCoverFile(manga)) + file.renameTo(cc.getCustomCoverFile(manga.manga)) } - manga.thumbnail_url = - manga.thumbnail_url!!.lowercase(Locale.ROOT).substringAfter("custom-") - updateManga.await(MangaUpdate(manga.id!!, thumbnailUrl = manga.thumbnail_url)) + manga.manga.thumbnail_url = + manga.manga.thumbnail_url!!.lowercase(Locale.ROOT).substringAfter("custom-") + updateManga.await(MangaUpdate(manga.manga.id!!, thumbnailUrl = manga.manga.thumbnail_url)) } } } @@ -1667,7 +1701,7 @@ class LibraryPresenter( data class LibraryData( val categories: List, val allCategories: List, - val items: List, + val items: LibraryMap, val hiddenItems: List, val removeArticles: Boolean, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt index 9950858541..94d55f515c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt @@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.domain.manga.models.Manga import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.library.LibraryGroup +import eu.kanade.tachiyomi.ui.library.LibraryMangaItem import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.launchUI @@ -368,11 +369,12 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri suspend fun checkForManhwa(sourceManager: SourceManager) { if (checked) return withIOContext { - val libraryManga = controller?.presenter?.allLibraryItems ?: return@withIOContext + val libraryManga = controller?.presenter?.currentLibraryItems ?: return@withIOContext checked = true var types = mutableSetOf() libraryManga.forEach { - when (it.manga.seriesType(sourceManager = sourceManager)) { + if (it !is LibraryMangaItem) return@forEach + when (it.manga.manga.seriesType(sourceManager = sourceManager)) { Manga.TYPE_MANHWA, Manga.TYPE_WEBTOON -> types.add(MR.strings.manhwa) Manga.TYPE_MANHUA -> types.add(MR.strings.manhua) Manga.TYPE_COMIC -> types.add(MR.strings.comic) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt index 054669930b..113df828cd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsController.kt @@ -28,9 +28,9 @@ import eu.kanade.tachiyomi.util.system.roundToTwoDecimal import eu.kanade.tachiyomi.util.view.compatToolTipText import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.withFadeTransaction +import kotlin.math.roundToInt import yokai.i18n.MR import yokai.util.lang.getString -import kotlin.math.roundToInt import android.R as AR class StatsController : BaseLegacyController() { @@ -61,7 +61,7 @@ class StatsController : BaseLegacyController() { } private fun handleGeneralStats() { - val mangaTracks = mangaDistinct.map { it to presenter.getTracks(it) } + val mangaTracks = mangaDistinct.map { it to presenter.getTracks(it.manga) } scoresList = getScoresList(mangaTracks) with(binding) { viewDetailLayout.isVisible = mangaDistinct.isNotEmpty() @@ -76,8 +76,8 @@ class StatsController : BaseLegacyController() { } statsTrackedMangaText.text = mangaTracks.count { it.second.isNotEmpty() }.toString() statsChaptersDownloadedText.text = mangaDistinct.sumOf { presenter.getDownloadCount(it) }.toString() - statsTotalTagsText.text = mangaDistinct.flatMap { it.getTags() }.distinct().count().toString() - statsMangaLocalText.text = mangaDistinct.count { it.isLocal() }.toString() + statsTotalTagsText.text = mangaDistinct.flatMap { it.manga.getTags() }.distinct().count().toString() + statsMangaLocalText.text = mangaDistinct.count { it.manga.isLocal() }.toString() statsGlobalUpdateMangaText.text = presenter.getGlobalUpdateManga().count().toString() statsSourcesText.text = presenter.getSources().count().toString() statsTrackersText.text = presenter.getLoggedTrackers().count().toString() @@ -105,7 +105,7 @@ class StatsController : BaseLegacyController() { val pieEntries = ArrayList() val mangaStatusDistributionList = statusMap.mapNotNull { (status, color) -> - val libraryCount = mangaDistinct.count { it.status == status } + val libraryCount = mangaDistinct.count { it.manga.status == status } if (status == SManga.UNKNOWN && libraryCount == 0) return@mapNotNull null pieEntries.add(PieEntry(libraryCount.toFloat(), activity!!.mapStatus(status))) StatusDistributionItem(activity!!.mapStatus(status), libraryCount, color) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt index 71cb38068c..6d7c2f3dc2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/StatsPresenter.kt @@ -65,19 +65,19 @@ class StatsPresenter( val includedCategories = prefs.libraryUpdateCategories().get().map(String::toInt) val excludedCategories = prefs.libraryUpdateCategoriesExclude().get().map(String::toInt) val restrictions = prefs.libraryUpdateMangaRestriction().get() - return libraryMangas.groupBy { it.id } + return libraryMangas.groupBy { it.manga.id } .filterNot { it.value.any { manga -> manga.category in excludedCategories } } .filter { includedCategories.isEmpty() || it.value.any { manga -> manga.category in includedCategories } } .filterNot { val manga = it.value.first() - (MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED) || + (MANGA_NON_COMPLETED in restrictions && manga.manga.status == SManga.COMPLETED) || (MANGA_HAS_UNREAD in restrictions && manga.unread != 0) || (MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasRead) } } fun getDownloadCount(manga: LibraryManga): Int { - return downloadManager.getDownloadCount(manga) + return downloadManager.getDownloadCount(manga.manga) } fun get10PointScore(track: Track): Float? { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt index d47536ffb9..2dc2160d22 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/stats/details/StatsDetailsPresenter.kt @@ -153,7 +153,7 @@ class StatsDetailsPresenter( private suspend fun setupSeriesType() { currentStats = ArrayList() - val libraryFormat = mangasDistinct.filterByChip().groupBy { it.seriesType() } + val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.seriesType() } libraryFormat.forEach { (seriesType, mangaList) -> currentStats?.add( @@ -173,7 +173,7 @@ class StatsDetailsPresenter( private suspend fun setupStatus() { currentStats = ArrayList() - val libraryFormat = mangasDistinct.filterByChip().groupBy { it.status } + val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.status } libraryFormat.forEach { (status, mangaList) -> currentStats?.add( @@ -263,7 +263,7 @@ class StatsDetailsPresenter( private suspend fun setupTrackers() { currentStats = ArrayList() val libraryFormat = mangasDistinct.filterByChip() - .map { it to getTracks(it).ifEmpty { listOf(null) } } + .map { it to getTracks(it.manga).ifEmpty { listOf(null) } } .flatMap { it.second.map { track -> it.first to track } } val loggedServices = trackManager.services.filter { it.isLogged } @@ -292,7 +292,7 @@ class StatsDetailsPresenter( private suspend fun setupSources() { currentStats = ArrayList() - val libraryFormat = mangasDistinct.filterByChip().groupBy { it.source } + val libraryFormat = mangasDistinct.filterByChip().groupBy { it.manga.source } libraryFormat.forEach { (sourceId, mangaList) -> val source = sourceManager.getOrStub(sourceId) @@ -339,10 +339,10 @@ class StatsDetailsPresenter( private suspend fun setupTags() { currentStats = ArrayList() val mangaFiltered = mangasDistinct.filterByChip() - val tags = mangaFiltered.flatMap { it.getTags() }.distinctBy { it.uppercase() } + val tags = mangaFiltered.flatMap { it.manga.getTags() }.distinctBy { it.uppercase() } val libraryFormat = tags.map { tag -> tag to mangaFiltered.filter { - it.getTags().any { mangaTag -> mangaTag.equals(tag, true) } + it.manga.getTags().any { mangaTag -> mangaTag.equals(tag, true) } } } @@ -433,7 +433,7 @@ class StatsDetailsPresenter( this } else { filter { manga -> - context.mapSeriesType(manga.seriesType()) in selectedSeriesType + context.mapSeriesType(manga.manga.seriesType()) in selectedSeriesType } } } @@ -443,7 +443,7 @@ class StatsDetailsPresenter( this } else { filter { manga -> - context.mapStatus(manga.status) in selectedStatus + context.mapStatus(manga.manga.status) in selectedStatus } } } @@ -463,7 +463,7 @@ class StatsDetailsPresenter( this } else { filter { manga -> - manga.source in selectedSource.map { it.id } + manga.manga.source in selectedSource.map { it.id } } } } @@ -504,10 +504,10 @@ class StatsDetailsPresenter( * Get language name of a manga */ private fun LibraryManga.getLanguage(): String { - val code = if (isLocal()) { - LocalSource.getMangaLang(this) + val code = if (manga.isLocal()) { + LocalSource.getMangaLang(this.manga) } else { - sourceManager.get(source)?.lang + sourceManager.get(manga.source)?.lang } ?: return context.getString(MR.strings.unknown) return LocaleHelper.getLocalizedDisplayName(code) } @@ -516,7 +516,7 @@ class StatsDetailsPresenter( * Get mean score rounded to two decimal of a list of manga */ private suspend fun List.getMeanScoreRounded(): Double? { - val mangaTracks = this.map { it to getTracks(it) } + val mangaTracks = this.map { it to getTracks(it.manga) } val scoresList = mangaTracks.filter { it.second.isNotEmpty() } .mapNotNull { it.second.getMeanScoreByTracker() } return if (scoresList.isEmpty()) null else scoresList.average().roundToTwoDecimal() @@ -526,7 +526,7 @@ class StatsDetailsPresenter( * Get mean score rounded to int of a single manga */ private suspend fun LibraryManga.getMeanScoreToInt(): Int? { - val mangaTracks = getTracks(this) + val mangaTracks = getTracks(this.manga) val scoresList = mangaTracks.filter { it.score > 0 } .mapNotNull { it.get10PointScore() } return if (scoresList.isEmpty()) null else scoresList.average().roundToInt().coerceIn(1..10) @@ -550,8 +550,8 @@ class StatsDetailsPresenter( } private suspend fun LibraryManga.getStartYear(): Int? { - if (getChapter.awaitAll(id!!, false).any { it.read }) { - val chapters = getHistory.awaitAllByMangaId(id!!).filter { it.last_read > 0 } + if (getChapter.awaitAll(manga.id!!, false).any { it.read }) { + val chapters = getHistory.awaitAllByMangaId(manga.id!!).filter { it.last_read > 0 } val date = chapters.minOfOrNull { it.last_read } ?: return null val cal = Calendar.getInstance().apply { timeInMillis = date } return if (date <= 0L) null else cal.get(Calendar.YEAR) @@ -564,7 +564,7 @@ class StatsDetailsPresenter( } private fun getEnabledSources(): List { - return mangasDistinct.mapNotNull { sourceManager.get(it.source) } + return mangasDistinct.mapNotNull { sourceManager.get(it.manga.source) } .distinct().sortedBy { it.name } } @@ -589,7 +589,7 @@ class StatsDetailsPresenter( } private suspend fun List.getReadDuration(): Long { - return sumOf { manga -> getHistory.awaitAllByMangaId(manga.id!!).sumOf { it.time_read } } + return sumOf { manga -> getHistory.awaitAllByMangaId(manga.manga.id!!).sumOf { it.time_read } } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index e2ec854e73..d2e6e31b04 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -335,7 +335,7 @@ class ReaderViewModel( val info = delegatedSource.fetchMangaFromChapterUrl(url) if (info != null) { val (sChapter, sManga, chapters) = info - val manga = Manga.create(sourceId).apply { copyFrom(sManga) } + val manga = Manga.create(sManga.url, sManga.title, sourceId).apply { copyFrom(sManga) } val chapter = Chapter.create().apply { copyFrom(sChapter) } val id = insertManga.await(manga) manga.id = id ?: manga.id diff --git a/app/src/main/java/yokai/domain/library/custom/model/CustomMangaInfo.kt b/app/src/main/java/yokai/domain/library/custom/model/CustomMangaInfo.kt index 9076e6c3b8..789dd4be01 100644 --- a/app/src/main/java/yokai/domain/library/custom/model/CustomMangaInfo.kt +++ b/app/src/main/java/yokai/domain/library/custom/model/CustomMangaInfo.kt @@ -12,8 +12,7 @@ data class CustomMangaInfo( val genre: String? = null, val status: Int? = null, ) { - fun toManga() = MangaImpl().apply { - id = this@CustomMangaInfo.mangaId + fun toManga() = MangaImpl(id = this.mangaId).apply { title = this@CustomMangaInfo.title ?: "" author = this@CustomMangaInfo.author artist = this@CustomMangaInfo.artist diff --git a/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt b/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt index da8d99f67b..05a84e0a2a 100644 --- a/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt +++ b/domain/src/commonMain/kotlin/eu/kanade/tachiyomi/domain/manga/models/Manga.kt @@ -82,9 +82,6 @@ interface Manga : SManga { } } - fun isBlank() = id == Long.MIN_VALUE - fun isHidden() = status == -1 - fun setChapterOrder(sorting: Int, order: Int) { setChapterFlags(sorting, CHAPTER_SORTING_MASK) setChapterFlags(order, CHAPTER_SORT_MASK) From 968639a59b60b36264681f8b86e3950102202941 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 5 Jan 2025 18:16:21 +0700 Subject: [PATCH 076/181] chore: Fix typo --- .../java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index f1a526adb3..d2d766fc68 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -740,7 +740,7 @@ class LibraryPresenter( values.sortedWith(Comparator(sortFn)) }.toSortedMap { category, category2 -> - // Force default category to already be at the top. This also for some reason fixed a bug where Default + // Force default category to always be at the top. This also for some reason fixed a bug where Default // category would disappear whenever a new category is added. if (category.id == 0) { -1 From eebc3dc8227bfd2d74248d1b4f1d4f94117f1bb4 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 5 Jan 2025 18:20:08 +0700 Subject: [PATCH 077/181] docs: Sync changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 180950ab91..5b3e22f176 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co ### Fixes - Allow users to bypass onboarding's permission step if Shizuku is installed - Fix Recents page shows "No recent chapters" instead of a loading screen +- Fix not fully loaded entries can't be selected on Library page ### Other - Refactor Library to utilize Flow even more @@ -28,6 +29,9 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Remove internal API usage to retrieve Kotlin version for kotlin-stdlib - Move :core module to :core:main - Move archive related code to :core:archive (@AntsyLich) +- Refactor Library to store LibraryMap instead of flatten list of LibraryItem + - LibraryItem abstraction to make it easier to manage + - LibraryManga no longer extend MangaImpl ## [1.9.7] From 7fc95e3029d14dcb1a685c6306650b93b2650a3f Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 5 Jan 2025 19:18:08 +0700 Subject: [PATCH 078/181] feat: Random sort --- CHANGELOG.md | 3 ++ .../ui/library/LibraryHeaderHolder.kt | 36 +++++++++---------- .../tachiyomi/ui/library/LibraryPresenter.kt | 33 ++++++++++++----- .../tachiyomi/ui/library/LibrarySort.kt | 13 ++++--- .../java/yokai/core/di/PreferenceModule.kt | 3 ++ .../domain/library/LibraryPreferences.kt | 7 ++++ app/src/main/res/drawable/ic_shuffle_24dp.xml | 5 +++ .../moko-resources/base/strings.xml | 1 + 8 files changed, 68 insertions(+), 33 deletions(-) create mode 100644 app/src/main/java/yokai/domain/library/LibraryPreferences.kt create mode 100644 app/src/main/res/drawable/ic_shuffle_24dp.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b3e22f176..6f419a2625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co ## [Unreleased] +### Additions +- Add random library sort + ### Fixes - Allow users to bypass onboarding's permission step if Shizuku is installed - Fix Recents page shows "No recent chapters" instead of a loading screen diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt index 9ef0bd1db5..2dc76b726a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt @@ -32,14 +32,17 @@ import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.view.compatToolTipText import eu.kanade.tachiyomi.util.view.setText import eu.kanade.tachiyomi.util.view.text +import kotlin.random.Random import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import yokai.domain.library.LibraryPreferences import yokai.i18n.MR import yokai.util.lang.getString class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) : BaseFlexibleViewHolder(view, adapter, true) { + private val libraryPreferences: LibraryPreferences = Injekt.get() private val binding = LibraryCategoryHeaderItemBinding.bind(view) val progressDrawableStart = CircularProgressDrawable(itemView.context) val progressDrawableEnd = CircularProgressDrawable(itemView.context) @@ -292,6 +295,7 @@ class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) : sortMode ?: return defaultDrawableRes return when (sortMode) { LibrarySort.DragAndDrop -> defaultDrawableRes + LibrarySort.Random -> R.drawable.ic_shuffle_24dp else -> { if (if (sortMode.hasInvertedSort) !isAscending else isAscending) { R.drawable.ic_arrow_downward_24dp @@ -306,35 +310,27 @@ class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) : sortingMode: Int?, isAscending: Boolean, @DrawableRes defaultDrawableRes: Int = R.drawable.ic_check_24dp, - ): Int { - sortingMode ?: return defaultDrawableRes - return when (val sortMode = LibrarySort.valueOf(sortingMode)) { - LibrarySort.DragAndDrop -> defaultDrawableRes - else -> { - if (if (sortMode?.hasInvertedSort == true) !isAscending else isAscending) { - R.drawable.ic_arrow_downward_24dp - } else { - R.drawable.ic_arrow_upward_24dp - } - } - } - } + ): Int = getSortRes(sortingMode?.let { LibrarySort.valueOf(it) }, isAscending, defaultDrawableRes) private fun onCatSortClicked(category: Category, menuId: Int?) { - val modType = if (menuId == null) { + val (mode, modType) = if (menuId == null) { val sortingMode = category.sortingMode() ?: LibrarySort.Title - if (category.isAscending()) { - sortingMode.categoryValueDescending - } else { - sortingMode.categoryValue - } + sortingMode to + if (sortingMode != LibrarySort.Random && category.isAscending()) { + sortingMode.categoryValueDescending + } else { + sortingMode.categoryValue + } } else { val sortingMode = LibrarySort.valueOf(menuId) ?: LibrarySort.Title if (sortingMode != LibrarySort.DragAndDrop && sortingMode == category.sortingMode()) { onCatSortClicked(category, null) return } - sortingMode.categoryValue + sortingMode to sortingMode.categoryValue + } + if (mode == LibrarySort.Random) { + libraryPreferences.randomSortSeed().set(Random.nextInt()) } adapter.libraryListener?.sortCategory(category.id!!, modType) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index d2d766fc68..e5d8db30c1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -73,6 +73,7 @@ import yokai.domain.chapter.interactor.GetChapter import yokai.domain.chapter.interactor.UpdateChapter import yokai.domain.chapter.models.ChapterUpdate import yokai.domain.history.interactor.GetHistory +import yokai.domain.library.LibraryPreferences import yokai.domain.manga.interactor.GetLibraryManga import yokai.domain.manga.interactor.GetManga import yokai.domain.manga.interactor.UpdateManga @@ -90,6 +91,7 @@ typealias LibraryMutableMap = MutableMap> */ class LibraryPresenter( private val preferences: PreferencesHelper = Injekt.get(), + private val libraryPreferences: LibraryPreferences = Injekt.get(), private val coverCache: CoverCache = Injekt.get(), val sourceManager: SourceManager = Injekt.get(), private val downloadCache: DownloadCache = Injekt.get(), @@ -697,6 +699,9 @@ class LibraryPresenter( sortAlphabetical(i1, i2) } } + LibrarySort.Random -> { + error("You're not supposed to be here...") + } } if (!category.isAscending()) sort *= -1 sort @@ -738,16 +743,28 @@ class LibraryPresenter( } } + if (LibrarySort.valueOf(category.mangaSort) == LibrarySort.Random) { + return@mapValues values + .asSequence() + .shuffled(Random(libraryPreferences.randomSortSeed().get())) + .sortedWith { i1, i2 -> + when { + i1 is LibraryPlaceholderItem -> -1 + i2 is LibraryPlaceholderItem -> 1 + else -> 0 + } + } + .toList() + } + values.sortedWith(Comparator(sortFn)) }.toSortedMap { category, category2 -> - // Force default category to always be at the top. This also for some reason fixed a bug where Default - // category would disappear whenever a new category is added. - if (category.id == 0) { - -1 - } else if (category2.id == 0) { - 1 - } else { - category.order.compareTo(category2.order) + when { + // Force default category to always be at the top. This also for some reason fixed a bug where Default + // category would disappear whenever a new category is added. + category.id == 0 -> -1 + category2.id == 0 -> 1 + else -> category.order.compareTo(category2.order) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt index 0d5ae1dd7d..ded5ecea84 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt @@ -1,13 +1,10 @@ package eu.kanade.tachiyomi.ui.library import androidx.annotation.DrawableRes -import androidx.annotation.StringRes import dev.icerock.moko.resources.StringResource import eu.kanade.tachiyomi.R -import yokai.i18n.MR -import yokai.util.lang.getString -import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.ui.base.MaterialMenuSheet +import yokai.i18n.MR enum class LibrarySort( val mainValue: Int, @@ -33,7 +30,11 @@ enum class LibrarySort( MR.strings.category, R.drawable.ic_label_outline_24dp, ), - + Random( + 8, + MR.strings.random, + R.drawable.ic_shuffle_24dp, + ) ; val categoryValue: Char @@ -50,6 +51,7 @@ enum class LibrarySort( LatestChapter -> "LATEST_CHAPTER" DateFetched -> "CHAPTER_FETCH_DATE" DateAdded -> "DATE_ADDED" + Random -> "RANDOM" else -> "ALPHABETICAL" } return "$type,ASCENDING" @@ -85,6 +87,7 @@ enum class LibrarySort( "LATEST_CHAPTER" -> LatestChapter "CHAPTER_FETCH_DATE" -> DateFetched "DATE_ADDED" -> DateAdded + "RANDOM" -> Random else -> Title } } catch (e: Exception) { diff --git a/app/src/main/java/yokai/core/di/PreferenceModule.kt b/app/src/main/java/yokai/core/di/PreferenceModule.kt index b309579312..a9cf3a5c46 100644 --- a/app/src/main/java/yokai/core/di/PreferenceModule.kt +++ b/app/src/main/java/yokai/core/di/PreferenceModule.kt @@ -13,6 +13,7 @@ import org.koin.dsl.module import yokai.domain.backup.BackupPreferences import yokai.domain.base.BasePreferences import yokai.domain.download.DownloadPreferences +import yokai.domain.library.LibraryPreferences import yokai.domain.recents.RecentsPreferences import yokai.domain.source.SourcePreferences import yokai.domain.storage.StoragePreferences @@ -47,6 +48,8 @@ fun preferenceModule(application: Application) = module { single { BackupPreferences(get()) } + single { LibraryPreferences(get()) } + single { PreferencesHelper( context = application, diff --git a/app/src/main/java/yokai/domain/library/LibraryPreferences.kt b/app/src/main/java/yokai/domain/library/LibraryPreferences.kt new file mode 100644 index 0000000000..e2dc5342f3 --- /dev/null +++ b/app/src/main/java/yokai/domain/library/LibraryPreferences.kt @@ -0,0 +1,7 @@ +package yokai.domain.library + +import eu.kanade.tachiyomi.core.preference.PreferenceStore + +class LibraryPreferences(private val preferenceStore: PreferenceStore) { + fun randomSortSeed() = preferenceStore.getInt("library_random_sort_seed", 0) +} diff --git a/app/src/main/res/drawable/ic_shuffle_24dp.xml b/app/src/main/res/drawable/ic_shuffle_24dp.xml new file mode 100644 index 0000000000..425564c27c --- /dev/null +++ b/app/src/main/res/drawable/ic_shuffle_24dp.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 84d9b5cdab..303e9324fa 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -174,6 +174,7 @@ Date fetched Latest chapter Drag & Drop + Random Display options From 568859891a78566235b46d12a89c728a6032b8fd Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 5 Jan 2025 19:27:29 +0700 Subject: [PATCH 079/181] fix: Forgor about the bubble --- .../eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index db4390ece6..4bd14503bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -270,6 +270,9 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : } getFirstLetter(title) } + LibrarySort.Random -> { + context.getString(MR.strings.random) + } } if (!isSingleCategory) { vibrateOnCategoryChange(item.header?.category?.name.orEmpty()) From 0565fc2665df1c47e338f7788fb1fbe05a2e450e Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 6 Jan 2025 08:02:00 +0700 Subject: [PATCH 080/181] fix: Selected icon for Random should be a reload icon --- .../data/database/models/Category.kt | 3 +- .../ui/library/LibraryHeaderHolder.kt | 56 ++++++++++--------- .../tachiyomi/ui/library/LibrarySort.kt | 5 +- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt index 3c4763dbfc..d9969b931b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Category.kt @@ -37,8 +37,7 @@ interface Category : Serializable { return ((mangaSort?.minus('a') ?: 0) % 2) != 1 } - fun sortingMode(nullAsDND: Boolean = false): LibrarySort? = LibrarySort.valueOf(mangaSort) - ?: if (nullAsDND && !isDynamic) LibrarySort.DragAndDrop else null + fun sortingMode(): LibrarySort? = LibrarySort.valueOf(mangaSort) val isDragAndDrop get() = ( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt index 2dc76b726a..435c209366 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt @@ -201,7 +201,7 @@ class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) : val isAscending = category.isAscending() val sortingMode = category.sortingMode() - val sortDrawable = getSortRes(sortingMode, isAscending, R.drawable.ic_sort_24dp) + val sortDrawable = getSortRes(sortingMode, isAscending, category.isDynamic, false) binding.categorySort.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, sortDrawable, 0) binding.categorySort.setText(category.sortRes()) @@ -266,7 +266,7 @@ class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) : val cat = category ?: return adapter.controller?.activity?.let { activity -> val items = LibrarySort.entries.map { it.menuSheetItem(cat.isDynamic) } - val sortingMode = cat.sortingMode(true) + val sortingMode = cat.sortingMode() ?: if (!cat.isDynamic) LibrarySort.DragAndDrop else null val sheet = MaterialMenuSheet( activity, items, @@ -275,42 +275,48 @@ class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) : ) { sheet, item -> onCatSortClicked(cat, item) val nCategory = (adapter.getItem(flexibleAdapterPosition) as? LibraryHeaderItem)?.category - val isAscending = nCategory?.isAscending() ?: false - val drawableRes = getSortRes(item, isAscending) - sheet.setDrawable(item, drawableRes) + sheet.updateSortIcon(nCategory, LibrarySort.valueOf(item)) false } - val isAscending = cat.isAscending() - val drawableRes = getSortRes(sortingMode, isAscending) - sheet.setDrawable(sortingMode?.mainValue ?: -1, drawableRes) + sheet.updateSortIcon(cat, sortingMode) sheet.show() } } + private fun MaterialMenuSheet.updateSortIcon(category: Category?, sortingMode: LibrarySort?) { + val isAscending = category?.isAscending() ?: false + val drawableRes = getSortRes(sortingMode, isAscending, category?.isDynamic ?: false, true) + this.setDrawable(sortingMode?.mainValue ?: -1, drawableRes) + } + private fun getSortRes( sortMode: LibrarySort?, isAscending: Boolean, - @DrawableRes defaultDrawableRes: Int = R.drawable.ic_check_24dp, + isDynamic: Boolean, + onSelection: Boolean, + @DrawableRes defaultDrawableRes: Int = R.drawable.ic_sort_24dp, + @DrawableRes defaultSelectedDrawableRes: Int = R.drawable.ic_check_24dp, ): Int { - sortMode ?: return defaultDrawableRes - return when (sortMode) { - LibrarySort.DragAndDrop -> defaultDrawableRes - LibrarySort.Random -> R.drawable.ic_shuffle_24dp - else -> { - if (if (sortMode.hasInvertedSort) !isAscending else isAscending) { - R.drawable.ic_arrow_downward_24dp - } else { - R.drawable.ic_arrow_upward_24dp - } + sortMode ?: return if (onSelection) defaultSelectedDrawableRes else defaultDrawableRes + + if (sortMode.isDirectional) { + return if (if (sortMode.hasInvertedSort) !isAscending else isAscending) { + R.drawable.ic_arrow_downward_24dp + } else { + R.drawable.ic_arrow_upward_24dp } } - } - private fun getSortRes( - sortingMode: Int?, - isAscending: Boolean, - @DrawableRes defaultDrawableRes: Int = R.drawable.ic_check_24dp, - ): Int = getSortRes(sortingMode?.let { LibrarySort.valueOf(it) }, isAscending, defaultDrawableRes) + if (onSelection) { + return when(sortMode) { + LibrarySort.DragAndDrop -> R.drawable.ic_check_24dp + LibrarySort.Random -> R.drawable.ic_refresh_24dp + else -> defaultSelectedDrawableRes + } + } + + return sortMode.iconRes(isDynamic) + } private fun onCatSortClicked(category: Category, menuId: Int?) { val (mode, modType) = if (menuId == null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt index ded5ecea84..460906a65b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt @@ -34,7 +34,7 @@ enum class LibrarySort( 8, MR.strings.random, R.drawable.ic_shuffle_24dp, - ) + ), ; val categoryValue: Char @@ -65,6 +65,9 @@ enum class LibrarySort( val hasInvertedSort: Boolean get() = this in listOf(LastRead, DateAdded, LatestChapter, DateFetched) + val isDirectional: Boolean + get() = this !in listOf(DragAndDrop, Random) + fun menuSheetItem(isDynamic: Boolean): MaterialMenuSheet.MenuSheetItem { return MaterialMenuSheet.MenuSheetItem( mainValue, From 6a680facd50e6adb009df1a76625292cefd64185 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 7 Jan 2025 05:45:50 +0700 Subject: [PATCH 081/181] refactor(extension): Installer abstraction --- .../tachiyomi/extension/ExtensionManager.kt | 6 +- .../extension/installer/Installer.kt | 122 +++++++++++++++++ .../{ => installer}/ShizukuInstaller.kt | 127 +++--------------- .../extension/util/ExtensionInstaller.kt | 4 +- .../controllers/SettingsAdvancedController.kt | 2 +- 5 files changed, 146 insertions(+), 115 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt rename app/src/main/java/eu/kanade/tachiyomi/extension/{ => installer}/ShizukuInstaller.kt (57%) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index 3492704094..d128020810 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -7,6 +7,7 @@ import android.os.Parcelable import co.touchlab.kermit.Logger import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.extension.api.ExtensionApi +import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.extension.model.LoadResult @@ -17,6 +18,9 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo import eu.kanade.tachiyomi.util.system.launchNow import eu.kanade.tachiyomi.util.system.withIOContext +import java.util.Date +import java.util.Locale +import java.util.concurrent.TimeUnit import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.flow.Flow @@ -27,8 +31,6 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import yokai.domain.base.BasePreferences import yokai.domain.extension.interactor.TrustExtension -import java.util.* -import java.util.concurrent.* /** * The manager of extensions installed as another apk which extend the available sources. It handles diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt new file mode 100644 index 0000000000..f759a572ee --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt @@ -0,0 +1,122 @@ +package eu.kanade.tachiyomi.extension.installer + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.annotation.CallSuper +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import eu.kanade.tachiyomi.extension.ExtensionManager +import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID +import java.util.Collections +import java.util.concurrent.atomic.AtomicReference +import uy.kohesive.injekt.injectLazy + +abstract class Installer( + internal val context: Context, + // TODO: Remove finishedQueue + internal val finishedQueue: (Installer) -> Unit, +) { + + private val extensionManager: ExtensionManager by injectLazy() + + private var waitingInstall = AtomicReference(null) + private val queue = Collections.synchronizedList(mutableListOf()) + + private val cancelReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return + cancelQueue(downloadId) + } + } + + abstract var ready: Boolean + + fun isInQueue(pkgName: String) = queue.any { it.pkgName == pkgName } + + /** + * Add an item to install queue. + * + * @param downloadId Download ID as known by [ExtensionManager] + * @param uri Uri of APK to install + */ + fun addToQueue(downloadId: Long, pkgName: String, uri: Uri) { + queue.add(Entry(downloadId, pkgName, uri)) + checkQueue() + } + + @CallSuper + open fun processEntry(entry: Entry) { + extensionManager.setInstalling(entry.downloadId, entry.uri.hashCode()) + } + + open fun cancelEntry(entry: Entry): Boolean { + return true + } + + /** + * Tells the queue to continue processing the next entry and updates the install step + * of the completed entry ([waitingInstall]) to [ExtensionManager]. + * + * @param resultStep new install step for the processed entry. + * @see waitingInstall + */ + fun continueQueue(succeeded: Boolean) { + val completedEntry = waitingInstall.getAndSet(null) + if (completedEntry != null) { + extensionManager.setInstallationResult(completedEntry.downloadId, succeeded) + checkQueue() + } + } + + fun checkQueue() { + if (!ready) { + return + } + if (queue.isEmpty()) { + finishedQueue(this) + return + } + val nextEntry = queue.first() + if (waitingInstall.compareAndSet(null, nextEntry)) { + queue.removeAt(0) + processEntry(nextEntry) + } + } + + @CallSuper + open fun onDestroy() { + LocalBroadcastManager.getInstance(context).unregisterReceiver(cancelReceiver) + queue.forEach { extensionManager.setInstallationResult(it.pkgName, false) } + queue.clear() + waitingInstall.set(null) + } + + protected fun getActiveEntry(): Entry? = waitingInstall.get() + + /** + * Cancels queue for the provided download ID if exists. + * + * @param downloadId Download ID as known by [ExtensionManager] + */ + fun cancelQueue(downloadId: Long) { + val waitingInstall = this.waitingInstall.get() + val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return + if (cancelEntry(toCancel)) { + queue.remove(toCancel) + if (waitingInstall == toCancel) { + // Currently processing removed entry, continue queue + this.waitingInstall.set(null) + checkQueue() + } + queue.forEach { extensionManager.setInstallationResult(it.downloadId, false) } +// extensionManager.up(downloadId, InstallStep.Idle) + } + } + + data class Entry( + val downloadId: Long, + val pkgName: String, + val uri: Uri, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt similarity index 57% rename from app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt index a19fd185c4..ae0307b6a2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt @@ -1,21 +1,16 @@ -package eu.kanade.tachiyomi.extension +package eu.kanade.tachiyomi.extension.installer -import android.content.BroadcastReceiver import android.content.Context -import android.content.Intent import android.content.pm.PackageManager -import android.net.Uri import android.os.Build import android.os.Process -import androidx.localbroadcastmanager.content.LocalBroadcastManager import co.touchlab.kermit.Logger -import eu.kanade.tachiyomi.R -import yokai.i18n.MR -import yokai.util.lang.getString -import dev.icerock.moko.resources.compose.stringResource -import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID import eu.kanade.tachiyomi.util.system.getUriSize import eu.kanade.tachiyomi.util.system.isShizukuInstalled +import java.io.BufferedReader +import java.io.InputStream +import java.lang.reflect.Method +import java.util.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -23,37 +18,21 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import rikka.shizuku.Shizuku import rikka.shizuku.ShizukuRemoteProcess -import uy.kohesive.injekt.injectLazy -import java.io.BufferedReader -import java.io.InputStream -import java.lang.reflect.Method -import java.util.* -import java.util.concurrent.atomic.AtomicReference +import yokai.i18n.MR +import yokai.util.lang.getString -class ShizukuInstaller(private val context: Context, val finishedQueue: (ShizukuInstaller) -> Unit) { +class ShizukuInstaller( + context: Context, + finishedQueue: (Installer) -> Unit, +) : Installer(context, finishedQueue) { - private val extensionManager: ExtensionManager by injectLazy() - - private var waitingInstall = AtomicReference(null) private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) - private val cancelReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return - cancelQueue(downloadId) - } - } - - data class Entry(val downloadId: Long, val pkgName: String, val uri: Uri) - private val queue = Collections.synchronizedList(mutableListOf()) - private val shizukuDeadListener = Shizuku.OnBinderDeadListener { Logger.d { "Shizuku was killed prematurely" } finishedQueue(this) } - fun isInQueue(pkgName: String) = queue.any { it.pkgName == pkgName } - private val shizukuPermissionListener = object : Shizuku.OnRequestPermissionResultListener { override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) { if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) { @@ -68,7 +47,7 @@ class ShizukuInstaller(private val context: Context, val finishedQueue: (Shizuku } } - var ready = false + override var ready = false private val newProcess: Method @@ -90,9 +69,8 @@ class ShizukuInstaller(private val context: Context, val finishedQueue: (Shizuku newProcess.isAccessible = true } - @Suppress("BlockingMethodInNonBlockingContext") - fun processEntry(entry: Entry) { - extensionManager.setInstalling(entry.downloadId, entry.uri.hashCode()) + override fun processEntry(entry: Entry) { + super.processEntry(entry) ioScope.launch { var sessionId: String? = null try { @@ -130,85 +108,14 @@ class ShizukuInstaller(private val context: Context, val finishedQueue: (Shizuku } } - /** - * Checks the queue. The provided service will be stopped if the queue is empty. - * Will not be run when not ready. - * - * @see ready - */ - fun checkQueue() { - if (!ready) { - return - } - if (queue.isEmpty()) { - finishedQueue(this) - return - } - val nextEntry = queue.first() - if (waitingInstall.compareAndSet(null, nextEntry)) { - queue.removeAt(0) - processEntry(nextEntry) - } - } - - /** - * Tells the queue to continue processing the next entry and updates the install step - * of the completed entry ([waitingInstall]) to [ExtensionManager]. - * - * @param resultStep new install step for the processed entry. - * @see waitingInstall - */ - fun continueQueue(succeeded: Boolean) { - val completedEntry = waitingInstall.getAndSet(null) - if (completedEntry != null) { - extensionManager.setInstallationResult(completedEntry.downloadId, succeeded) - checkQueue() - } - } - - /** - * Add an item to install queue. - * - * @param downloadId Download ID as known by [ExtensionManager] - * @param uri Uri of APK to install - */ - fun addToQueue(downloadId: Long, pkgName: String, uri: Uri) { - queue.add(Entry(downloadId, pkgName, uri)) - checkQueue() - } - - /** - * Cancels queue for the provided download ID if exists. - * - * @param downloadId Download ID as known by [ExtensionManager] - */ - private fun cancelQueue(downloadId: Long) { - val waitingInstall = this.waitingInstall.get() - val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return - if (cancelEntry(toCancel)) { - queue.remove(toCancel) - if (waitingInstall == toCancel) { - // Currently processing removed entry, continue queue - this.waitingInstall.set(null) - checkQueue() - } - queue.forEach { extensionManager.setInstallationResult(it.downloadId, false) } -// extensionManager.up(downloadId, InstallStep.Idle) - } - } - // Don't cancel if entry is already started installing - fun cancelEntry(entry: Entry): Boolean = getActiveEntry() != entry - fun getActiveEntry(): Entry? = waitingInstall.get() + override fun cancelEntry(entry: Entry): Boolean = getActiveEntry() != entry - fun onDestroy() { + override fun onDestroy() { Shizuku.removeBinderDeadListener(shizukuDeadListener) Shizuku.removeRequestPermissionResultListener(shizukuPermissionListener) ioScope.cancel() - LocalBroadcastManager.getInstance(context).unregisterReceiver(cancelReceiver) - queue.forEach { extensionManager.setInstallationResult(it.pkgName, false) } - queue.clear() - waitingInstall.set(null) + super.onDestroy() } private fun exec(command: String, stdin: InputStream? = null): ShellResult { diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt index 981d973c41..d97ad8de5b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt @@ -14,7 +14,7 @@ import androidx.core.net.toUri import co.touchlab.kermit.Logger import eu.kanade.tachiyomi.extension.ExtensionInstallerJob import eu.kanade.tachiyomi.extension.ExtensionManager -import eu.kanade.tachiyomi.extension.ShizukuInstaller +import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo import eu.kanade.tachiyomi.util.storage.getUriCompat @@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.util.system.e import eu.kanade.tachiyomi.util.system.isPackageInstalled import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.toast +import java.io.File import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -47,7 +48,6 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import yokai.domain.base.BasePreferences -import java.io.File /** * The installer which installs, updates and uninstalls the extensions. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt index 6f2616dd61..6f01d01dc4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt @@ -24,7 +24,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob.Target import eu.kanade.tachiyomi.data.preference.PreferenceKeys import eu.kanade.tachiyomi.data.preference.changesIn -import eu.kanade.tachiyomi.extension.ShizukuInstaller +import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.PREF_DOH_360 From d655c3e09ae2c1dd1333dbe7e1f4e2f9abb3e48f Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 7 Jan 2025 07:36:00 +0700 Subject: [PATCH 082/181] chore: WIP compose library page --- .../base/controller/BaseComposeController.kt | 4 +- .../ui/base/controller/BaseController.kt | 6 ++ .../base/controller/BaseLegacyController.kt | 4 +- .../ui/library/LibraryComposeController.kt | 23 ++++++++ .../kanade/tachiyomi/ui/main/MainActivity.kt | 3 +- .../controllers/SettingsAdvancedController.kt | 4 ++ .../java/yokai/domain/base/BasePreferences.kt | 2 + .../main/java/yokai/presentation/Scaffold.kt | 3 + .../presentation/library/LibraryScreen.kt | 33 +++++++++++ .../library/components/CommonMangaItem.kt | 11 ++++ .../library/components/LazyLibraryGrid.kt | 28 +++++++++ .../moko-resources/base/strings.xml | 2 + .../presentation/core/components/LazyGrid.kt | 58 +++++++++++++++++++ .../presentation/core/util/PaddingValues.kt | 22 +++++++ 14 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt create mode 100644 app/src/main/java/yokai/presentation/library/LibraryScreen.kt create mode 100644 app/src/main/java/yokai/presentation/library/components/CommonMangaItem.kt create mode 100644 app/src/main/java/yokai/presentation/library/components/LazyLibraryGrid.kt create mode 100644 presentation/core/src/main/java/yokai/presentation/core/components/LazyGrid.kt create mode 100644 presentation/core/src/main/java/yokai/presentation/core/util/PaddingValues.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt index 7f2fed8f1f..4ecd065463 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseComposeController.kt @@ -17,12 +17,14 @@ import yokai.presentation.theme.YokaiTheme abstract class BaseComposeController(bundle: Bundle? = null) : BaseController(bundle) { + override val shouldHideLegacyAppBar = true + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle? ): View { - hideLegacyAppBar() + setAppBarVisibility() return ComposeView(container.context).apply { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt index f378bed539..b1fcea81e2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt @@ -25,6 +25,8 @@ import kotlinx.coroutines.cancel abstract class BaseController(bundle: Bundle? = null) : Controller(bundle), BackHandlerControllerInterface, BaseControllerPreferenceControllerCommonInterface { + abstract val shouldHideLegacyAppBar: Boolean + lateinit var viewScope: CoroutineScope var isDragging = false @@ -58,6 +60,10 @@ abstract class BaseController(bundle: Bundle? = null) : open fun onViewCreated(view: View) { } + internal fun setAppBarVisibility() { + if (shouldHideLegacyAppBar) hideLegacyAppBar() else showLegacyAppBar() + } + override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { if (type.isEnter && !isControllerVisible) { view?.alpha = 0f diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseLegacyController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseLegacyController.kt index 996074df6f..e710deaf8d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseLegacyController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseLegacyController.kt @@ -20,11 +20,13 @@ import eu.kanade.tachiyomi.util.view.isControllerVisible abstract class BaseLegacyController(bundle: Bundle? = null) : BaseController(bundle) { + override val shouldHideLegacyAppBar = false + lateinit var binding: VB val isBindingInitialized get() = this::binding.isInitialized override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View { - showLegacyAppBar() + setAppBarVisibility() binding = createBinding(inflater) binding.root.backgroundColor = binding.root.context.getResourceColor(R.attr.background) return binding.root diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt new file mode 100644 index 0000000000..a67ecad114 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt @@ -0,0 +1,23 @@ +package eu.kanade.tachiyomi.ui.library + +import android.os.Bundle +import androidx.compose.runtime.Composable +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import yokai.domain.ui.UiPreferences +import yokai.presentation.library.LibraryScreen + +class LibraryComposeController( + bundle: Bundle? = null, + val uiPreferences: UiPreferences = Injekt.get(), + val preferences: PreferencesHelper = Injekt.get(), +) : BaseComposeController(bundle) { + override val shouldHideLegacyAppBar = false + + @Composable + override fun ScreenContent() { + LibraryScreen() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index cc0ecb6cc5..bed1662cda 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -85,6 +85,7 @@ import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.controller.BaseLegacyController import eu.kanade.tachiyomi.ui.base.controller.DialogController +import eu.kanade.tachiyomi.ui.library.LibraryComposeController import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.more.AboutController @@ -538,7 +539,7 @@ open class MainActivity : BaseActivity() { if (currentRoot?.tag()?.toIntOrNull() != id) { setRoot( when (id) { - R.id.nav_library -> LibraryController() + R.id.nav_library -> if (basePreferences.composeLibrary().get()) LibraryComposeController() else LibraryController() R.id.nav_recents -> RecentsController() else -> BrowseController() }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt index 6f01d01dc4..c1e7143314 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt @@ -394,6 +394,10 @@ class SettingsAdvancedController : SettingsLegacyController() { onClick { LibraryUpdateJob.startNow(context, target = Target.TRACKING) } } + switchPreference { + bindTo(basePreferences.composeLibrary()) + title = context.getString(MR.strings.pref_use_compose_library).addBetaTag(context) + } } preferenceCategory { diff --git a/app/src/main/java/yokai/domain/base/BasePreferences.kt b/app/src/main/java/yokai/domain/base/BasePreferences.kt index 5e450ee513..ca8e21ded1 100644 --- a/app/src/main/java/yokai/domain/base/BasePreferences.kt +++ b/app/src/main/java/yokai/domain/base/BasePreferences.kt @@ -49,4 +49,6 @@ class BasePreferences(private val preferenceStore: PreferenceStore) { } fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT) + + fun composeLibrary() = preferenceStore.getBoolean("pref_use_compose_library", false) } diff --git a/app/src/main/java/yokai/presentation/Scaffold.kt b/app/src/main/java/yokai/presentation/Scaffold.kt index 7122df5f38..4c63800a1e 100644 --- a/app/src/main/java/yokai/presentation/Scaffold.kt +++ b/app/src/main/java/yokai/presentation/Scaffold.kt @@ -99,6 +99,7 @@ fun YokaiScaffold( scrollBehavior = scrollBehaviorOrDefault, actions = actions, ) + AppBarType.NONE -> {} } }, snackbarHost = snackbarHost, @@ -115,6 +116,8 @@ fun getTopAppBarColor(title: String): Pair { } enum class AppBarType { + // FIXME: Delete "NONE" later + NONE, SMALL, LARGE, } diff --git a/app/src/main/java/yokai/presentation/library/LibraryScreen.kt b/app/src/main/java/yokai/presentation/library/LibraryScreen.kt new file mode 100644 index 0000000000..4e1abf5fad --- /dev/null +++ b/app/src/main/java/yokai/presentation/library/LibraryScreen.kt @@ -0,0 +1,33 @@ +package yokai.presentation.library + +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import yokai.presentation.AppBarType +import yokai.presentation.YokaiScaffold +import yokai.presentation.library.components.LazyLibraryGrid +import yokai.util.Screen + +class LibraryScreen : Screen() { + @Composable + override fun Content() { + val columns = 3 // FIXME: Retrieve from preferences + val items = (0..100).toList() + YokaiScaffold( + onNavigationIconClicked = {}, + appBarType = AppBarType.NONE, + ) { contentPadding -> + LazyLibraryGrid( + columns = columns, + contentPadding = contentPadding, + ) { + items( + items = items, + contentType = { "library_grid_item" } + ) { + Text("Hello world! ($it)") + } + } + } + } +} diff --git a/app/src/main/java/yokai/presentation/library/components/CommonMangaItem.kt b/app/src/main/java/yokai/presentation/library/components/CommonMangaItem.kt new file mode 100644 index 0000000000..cec16949c2 --- /dev/null +++ b/app/src/main/java/yokai/presentation/library/components/CommonMangaItem.kt @@ -0,0 +1,11 @@ +package yokai.presentation.library.components + +import androidx.compose.ui.unit.dp + +object CommonMangaItemDefaults { + val GridHorizontalSpacer = 4.dp + val GridVerticalSpacer = 4.dp + + @Suppress("ConstPropertyName") + const val BrowseFavoriteCoverAlpha = 0.34f +} diff --git a/app/src/main/java/yokai/presentation/library/components/LazyLibraryGrid.kt b/app/src/main/java/yokai/presentation/library/components/LazyLibraryGrid.kt new file mode 100644 index 0000000000..ae4541db99 --- /dev/null +++ b/app/src/main/java/yokai/presentation/library/components/LazyLibraryGrid.kt @@ -0,0 +1,28 @@ +package yokai.presentation.library.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import yokai.presentation.core.components.FastScrollLazyVerticalGrid +import yokai.presentation.core.util.plus + +@Composable +internal fun LazyLibraryGrid( + modifier: Modifier = Modifier, + columns: Int, + contentPadding: PaddingValues, + content: LazyGridScope.() -> Unit, +) { + FastScrollLazyVerticalGrid( + columns = if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns), + modifier = modifier, + contentPadding = contentPadding + PaddingValues(8.dp), + verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer), + horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer), + content = content, + ) +} diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 303e9324fa..7b5c85b035 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -166,6 +166,8 @@ Tracking status Ungrouped + Use experimental compose library + Sort by Total chapters diff --git a/presentation/core/src/main/java/yokai/presentation/core/components/LazyGrid.kt b/presentation/core/src/main/java/yokai/presentation/core/components/LazyGrid.kt new file mode 100644 index 0000000000..1532c990cc --- /dev/null +++ b/presentation/core/src/main/java/yokai/presentation/core/components/LazyGrid.kt @@ -0,0 +1,58 @@ +package yokai.presentation.core.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun FastScrollLazyVerticalGrid( + columns: GridCells, + modifier: Modifier = Modifier, + state: LazyGridState = rememberLazyGridState(), + thumbAllowed: () -> Boolean = { true }, + thumbColor: Color = MaterialTheme.colorScheme.primary, + contentPadding: PaddingValues = PaddingValues(0.dp), + topContentPadding: Dp = Dp.Hairline, + bottomContentPadding: Dp = Dp.Hairline, + endContentPadding: Dp = Dp.Hairline, + reverseLayout: Boolean = false, + verticalArrangement: Arrangement.Vertical = + if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, + horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, + userScrollEnabled: Boolean = true, + content: LazyGridScope.() -> Unit, +) { + VerticalGridFastScroller( + state = state, + columns = columns, + arrangement = horizontalArrangement, + contentPadding = contentPadding, + modifier = modifier, + thumbAllowed = thumbAllowed, + thumbColor = thumbColor, + topContentPadding = topContentPadding, + bottomContentPadding = bottomContentPadding, + endContentPadding = endContentPadding, + ) { + LazyVerticalGrid( + columns = columns, + state = state, + contentPadding = contentPadding, + reverseLayout = reverseLayout, + verticalArrangement = verticalArrangement, + horizontalArrangement = horizontalArrangement, + userScrollEnabled = userScrollEnabled, + content = content, + ) + } +} diff --git a/presentation/core/src/main/java/yokai/presentation/core/util/PaddingValues.kt b/presentation/core/src/main/java/yokai/presentation/core/util/PaddingValues.kt new file mode 100644 index 0000000000..8422a88ec4 --- /dev/null +++ b/presentation/core/src/main/java/yokai/presentation/core/util/PaddingValues.kt @@ -0,0 +1,22 @@ +package yokai.presentation.core.util + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.platform.LocalLayoutDirection + +@Composable +@ReadOnlyComposable +operator fun PaddingValues.plus(other: PaddingValues): PaddingValues { + val layoutDirection = LocalLayoutDirection.current + return PaddingValues( + start = calculateStartPadding(layoutDirection) + + other.calculateStartPadding(layoutDirection), + end = calculateEndPadding(layoutDirection) + + other.calculateEndPadding(layoutDirection), + top = calculateTopPadding() + other.calculateTopPadding(), + bottom = calculateBottomPadding() + other.calculateBottomPadding(), + ) +} From d0d322fd67dcd9962005002706313db9ac1ba263 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 7 Jan 2025 07:56:43 +0700 Subject: [PATCH 083/181] refactor(extension/repo): Use ScreenModel instead of ViewModel --- .../extension/repo/ExtensionRepoController.kt | 22 +- .../extension/repo/ExtensionRepoScreen.kt | 329 +++++++++--------- ...ewModel.kt => ExtensionRepoScreenModel.kt} | 55 ++- 3 files changed, 205 insertions(+), 201 deletions(-) rename app/src/main/java/yokai/presentation/extension/repo/{ExtensionRepoViewModel.kt => ExtensionRepoScreenModel.kt} (77%) diff --git a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoController.kt b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoController.kt index 109f6858df..f2e8cd923a 100644 --- a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoController.kt +++ b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoController.kt @@ -1,22 +1,22 @@ package yokai.presentation.extension.repo import androidx.compose.runtime.Composable +import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.transitions.CrossfadeTransition import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController -class ExtensionRepoController() : - BaseComposeController() { - - private var repoUrl: String? = null - - constructor(repoUrl: String) : this() { - this.repoUrl = repoUrl - } +class ExtensionRepoController(private val repoUrl: String? = null) : BaseComposeController() { @Composable override fun ScreenContent() { - ExtensionRepoScreen( - title = "Extension Repos", - repoUrl = repoUrl, + Navigator( + screen = ExtensionRepoScreen( + title = "Extension Repos", + repoUrl = repoUrl, + ), + content = { + CrossfadeTransition(navigator = it) + }, ) } } diff --git a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt index 2e347e2397..b85255cd00 100644 --- a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt +++ b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreen.kt @@ -25,7 +25,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.sp -import androidx.lifecycle.viewmodel.compose.viewModel +import cafe.adriel.voyager.core.model.rememberScreenModel import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.util.compose.LocalBackPress import eu.kanade.tachiyomi.util.compose.LocalDialogHostState @@ -43,190 +43,197 @@ import yokai.presentation.component.EmptyScreen import yokai.presentation.component.ToolTipButton import yokai.presentation.extension.repo.component.ExtensionRepoInput import yokai.presentation.extension.repo.component.ExtensionRepoItem +import yokai.util.Screen import android.R as AR -@Composable -fun ExtensionRepoScreen( - title: String, - viewModel: ExtensionRepoViewModel = viewModel(), - repoUrl: String? = null, -) { - val onBackPress = LocalBackPress.currentOrThrow - val context = LocalContext.current - val alertDialog = LocalDialogHostState.currentOrThrow - val scope = rememberCoroutineScope() +class ExtensionRepoScreen( + private val title: String, + private var repoUrl: String? = null, +): Screen() { + @Composable + override fun Content() { + val onBackPress = LocalBackPress.currentOrThrow + val context = LocalContext.current + val alertDialog = LocalDialogHostState.currentOrThrow - val repoState by viewModel.repoState.collectAsState() - var inputText by remember { mutableStateOf("") } - val listState = rememberLazyListState() + val scope = rememberCoroutineScope() + val screenModel = rememberScreenModel { ExtensionRepoScreenModel() } + val state by screenModel.state.collectAsState() - YokaiScaffold( - onNavigationIconClicked = onBackPress, - title = title, - appBarType = AppBarType.SMALL, - scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior( - state = rememberTopAppBarState(), - canScroll = { listState.firstVisibleItemIndex > 0 || listState.firstVisibleItemScrollOffset > 0 }, - ), - actions = { - ToolTipButton( - toolTipLabel = stringResource(MR.strings.refresh), - icon = Icons.Outlined.Refresh, - buttonClicked = { - context.toast("Refreshing...") // TODO: Should be loading animation instead - viewModel.refreshRepos() - }, - ) - }, - ) { innerPadding -> - if (repoState is ExtensionRepoState.Loading) return@YokaiScaffold + var inputText by remember { mutableStateOf("") } + val listState = rememberLazyListState() - val repos = (repoState as ExtensionRepoState.Success).repos - - alertDialog.value?.invoke() - - LazyColumn( - modifier = Modifier.padding(innerPadding), - userScrollEnabled = true, - verticalArrangement = Arrangement.Top, - state = listState, - ) { - item { - ExtensionRepoInput( - inputText = inputText, - inputHint = stringResource(MR.strings.label_add_repo), - onInputChange = { inputText = it }, - onAddClick = { viewModel.addRepo(it) }, + YokaiScaffold( + onNavigationIconClicked = onBackPress, + title = title, + appBarType = AppBarType.SMALL, + scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior( + state = rememberTopAppBarState(), + canScroll = { listState.firstVisibleItemIndex > 0 || listState.firstVisibleItemScrollOffset > 0 }, + ), + actions = { + ToolTipButton( + toolTipLabel = stringResource(MR.strings.refresh), + icon = Icons.Outlined.Refresh, + buttonClicked = { + context.toast("Refreshing...") // TODO: Should be loading animation instead + screenModel.refreshRepos() + }, ) - } + }, + ) { innerPadding -> + if (state is ExtensionRepoScreenModel.State.Loading) return@YokaiScaffold - if (repos.isEmpty()) { + val repos = (state as ExtensionRepoScreenModel.State.Success).repos + + alertDialog.value?.invoke() + + LazyColumn( + modifier = Modifier.padding(innerPadding), + userScrollEnabled = true, + verticalArrangement = Arrangement.Top, + state = listState, + ) { item { - EmptyScreen( - modifier = Modifier.fillParentMaxSize(), - image = Icons.Filled.ExtensionOff, - message = stringResource(MR.strings.information_empty_repos), - isTablet = isTablet(), + ExtensionRepoInput( + inputText = inputText, + inputHint = stringResource(MR.strings.label_add_repo), + onInputChange = { inputText = it }, + onAddClick = { screenModel.addRepo(it) }, ) } - return@LazyColumn - } - repos.forEach { repo -> - item { - ExtensionRepoItem( - extensionRepo = repo, - onDeleteClick = { repoToDelete -> - scope.launch { alertDialog.awaitExtensionRepoDeletePrompt(repoToDelete, viewModel) } - }, - ) + if (repos.isEmpty()) { + item { + EmptyScreen( + modifier = Modifier.fillParentMaxSize(), + image = Icons.Filled.ExtensionOff, + message = stringResource(MR.strings.information_empty_repos), + isTablet = isTablet(), + ) + } + return@LazyColumn + } + + repos.forEach { repo -> + item { + ExtensionRepoItem( + extensionRepo = repo, + onDeleteClick = { repoToDelete -> + scope.launch { alertDialog.awaitExtensionRepoDeletePrompt(repoToDelete, screenModel) } + }, + ) + } } } } - } - LaunchedEffect(repoUrl) { - repoUrl?.let { viewModel.addRepo(repoUrl) } - } - - LaunchedEffect(Unit) { - viewModel.event.collectLatest { event -> - when (event) { - is ExtensionRepoEvent.NoOp -> {} - is ExtensionRepoEvent.LocalizedMessage -> context.toast(event.stringRes) - is ExtensionRepoEvent.Success -> inputText = "" - is ExtensionRepoEvent.ShowDialog -> { - when(event.dialog) { - is RepoDialog.Conflict -> { - alertDialog.awaitExtensionRepoReplacePrompt( - oldRepo = event.dialog.oldRepo, - newRepo = event.dialog.newRepo, - onMigrate = { viewModel.replaceRepo(event.dialog.newRepo) }, - ) + LaunchedEffect(repoUrl) { + repoUrl?.let { + screenModel.addRepo(repoUrl!!) + repoUrl = null + } + } + + LaunchedEffect(Unit) { + screenModel.event.collectLatest { event -> + when (event) { + is ExtensionRepoEvent.NoOp -> {} + is ExtensionRepoEvent.LocalizedMessage -> context.toast(event.stringRes) + is ExtensionRepoEvent.Success -> inputText = "" + is ExtensionRepoEvent.ShowDialog -> { + when(event.dialog) { + is RepoDialog.Conflict -> { + alertDialog.awaitExtensionRepoReplacePrompt( + oldRepo = event.dialog.oldRepo, + newRepo = event.dialog.newRepo, + onMigrate = { screenModel.replaceRepo(event.dialog.newRepo) }, + ) + } } } } } } } -} -suspend fun DialogHostState.awaitExtensionRepoReplacePrompt( - oldRepo: ExtensionRepo, - newRepo: ExtensionRepo, - onMigrate: () -> Unit, -): Unit = dialog { cont -> - AlertDialog( - onDismissRequest = { cont.cancel() }, - confirmButton = { - TextButton( - onClick = { - onMigrate() - cont.cancel() - }, - ) { - Text(text = stringResource(MR.strings.action_replace_repo)) - } - }, - dismissButton = { - TextButton(onClick = { cont.cancel() }) { - Text(text = stringResource(AR.string.cancel)) - } - }, - title = { - Text(text = stringResource(MR.strings.action_replace_repo_title)) - }, - text = { - Text(text = stringResource(MR.strings.action_replace_repo_message, newRepo.name, oldRepo.name)) - }, - ) -} - -suspend fun DialogHostState.awaitExtensionRepoDeletePrompt( - repoToDelete: String, - viewModel: ExtensionRepoViewModel, -): Unit = dialog { cont -> - AlertDialog( - containerColor = MaterialTheme.colorScheme.surface, - title = { - Text( - text = stringResource(MR.strings.confirm_delete_repo_title), - fontStyle = MaterialTheme.typography.titleMedium.fontStyle, - color = MaterialTheme.colorScheme.onSurface, - fontSize = 24.sp, - ) - }, - text = { - Text( - text = stringResource(MR.strings.confirm_delete_repo, repoToDelete), - fontStyle = MaterialTheme.typography.bodyMedium.fontStyle, - color = MaterialTheme.colorScheme.onSurfaceVariant, - fontSize = 14.sp, - ) - }, - onDismissRequest = { cont.cancel() }, - confirmButton = { - TextButton( - onClick = { - viewModel.deleteRepo(repoToDelete) - cont.cancel() + private suspend fun DialogHostState.awaitExtensionRepoReplacePrompt( + oldRepo: ExtensionRepo, + newRepo: ExtensionRepo, + onMigrate: () -> Unit, + ): Unit = dialog { cont -> + AlertDialog( + onDismissRequest = { cont.cancel() }, + confirmButton = { + TextButton( + onClick = { + onMigrate() + cont.cancel() + }, + ) { + Text(text = stringResource(MR.strings.action_replace_repo)) } - ) { + }, + dismissButton = { + TextButton(onClick = { cont.cancel() }) { + Text(text = stringResource(AR.string.cancel)) + } + }, + title = { + Text(text = stringResource(MR.strings.action_replace_repo_title)) + }, + text = { + Text(text = stringResource(MR.strings.action_replace_repo_message, newRepo.name, oldRepo.name)) + }, + ) + } + + private suspend fun DialogHostState.awaitExtensionRepoDeletePrompt( + repoToDelete: String, + screenModel: ExtensionRepoScreenModel, + ): Unit = dialog { cont -> + AlertDialog( + containerColor = MaterialTheme.colorScheme.surface, + title = { Text( - text = stringResource(MR.strings.delete), - color = MaterialTheme.colorScheme.primary, + text = stringResource(MR.strings.confirm_delete_repo_title), + fontStyle = MaterialTheme.typography.titleMedium.fontStyle, + color = MaterialTheme.colorScheme.onSurface, + fontSize = 24.sp, + ) + }, + text = { + Text( + text = stringResource(MR.strings.confirm_delete_repo, repoToDelete), + fontStyle = MaterialTheme.typography.bodyMedium.fontStyle, + color = MaterialTheme.colorScheme.onSurfaceVariant, fontSize = 14.sp, ) - } - }, - dismissButton = { - TextButton(onClick = { cont.cancel() }) { - Text( - text = stringResource(MR.strings.cancel), - color = MaterialTheme.colorScheme.primary, - fontSize = 14.sp, - ) - } - }, - ) + }, + onDismissRequest = { cont.cancel() }, + confirmButton = { + TextButton( + onClick = { + screenModel.deleteRepo(repoToDelete) + cont.cancel() + } + ) { + Text( + text = stringResource(MR.strings.delete), + color = MaterialTheme.colorScheme.primary, + fontSize = 14.sp, + ) + } + }, + dismissButton = { + TextButton(onClick = { cont.cancel() }) { + Text( + text = stringResource(MR.strings.cancel), + color = MaterialTheme.colorScheme.primary, + fontSize = 14.sp, + ) + } + }, + ) + } } diff --git a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoViewModel.kt b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreenModel.kt similarity index 77% rename from app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoViewModel.kt rename to app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreenModel.kt index 58f3a6518a..372f13edc2 100644 --- a/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoViewModel.kt +++ b/app/src/main/java/yokai/presentation/extension/repo/ExtensionRepoScreenModel.kt @@ -1,8 +1,8 @@ package yokai.presentation.extension.repo import androidx.compose.runtime.Immutable -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.screenModelScope import dev.icerock.moko.resources.StringResource import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.util.system.launchIO @@ -22,8 +22,7 @@ import yokai.domain.extension.repo.interactor.UpdateExtensionRepo import yokai.domain.extension.repo.model.ExtensionRepo import yokai.i18n.MR -class ExtensionRepoViewModel : - ViewModel() { +class ExtensionRepoScreenModel : StateScreenModel(State.Loading) { private val extensionManager: ExtensionManager by injectLazy() @@ -33,23 +32,20 @@ class ExtensionRepoViewModel : private val replaceExtensionRepo: ReplaceExtensionRepo by injectLazy() private val updateExtensionRepo: UpdateExtensionRepo by injectLazy() - private val mutableRepoState: MutableStateFlow = MutableStateFlow(ExtensionRepoState.Loading) - val repoState: StateFlow = mutableRepoState.asStateFlow() - private val internalEvent: MutableStateFlow = MutableStateFlow(ExtensionRepoEvent.NoOp) val event: StateFlow = internalEvent.asStateFlow() init { - viewModelScope.launchIO { + screenModelScope.launchIO { getExtensionRepo.subscribeAll().collectLatest { repos -> - mutableRepoState.update { ExtensionRepoState.Success(repos = repos.toImmutableList()) } + mutableState.update { State.Success(repos = repos.toImmutableList()) } extensionManager.refreshTrust() } } } fun addRepo(url: String) { - viewModelScope.launchIO { + screenModelScope.launchIO { when (val result = createExtensionRepo.await(url)) { is CreateExtensionRepo.Result.Success -> internalEvent.value = ExtensionRepoEvent.Success is CreateExtensionRepo.Result.Error -> internalEvent.value = ExtensionRepoEvent.InvalidUrl @@ -63,26 +59,41 @@ class ExtensionRepoViewModel : } fun replaceRepo(newRepo: ExtensionRepo) { - viewModelScope.launchIO { + screenModelScope.launchIO { replaceExtensionRepo.await(newRepo) } } fun refreshRepos() { - val status = repoState.value + val status = state.value - if (status is ExtensionRepoState.Success) { - viewModelScope.launchIO { + if (status is State.Success) { + screenModelScope.launchIO { updateExtensionRepo.awaitAll() } } } fun deleteRepo(url: String) { - viewModelScope.launchIO { + screenModelScope.launchIO { deleteExtensionRepo.await(url) } } + + sealed interface State { + + @Immutable + data object Loading : State + + @Immutable + data class Success( + val repos: ImmutableList, + ) : State { + + val isEmpty: Boolean + get() = repos.isEmpty() + } + } } sealed class RepoDialog { @@ -98,17 +109,3 @@ sealed class ExtensionRepoEvent { data object Success : ExtensionRepoEvent() } -sealed class ExtensionRepoState { - - @Immutable - data object Loading : ExtensionRepoState() - - @Immutable - data class Success( - val repos: ImmutableList, - ) : ExtensionRepoState() { - - val isEmpty: Boolean - get() = repos.isEmpty() - } -} From c6c40ffb718a82b6b9a6dfdf09aa3daa41e0b028 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 7 Jan 2025 08:18:50 +0700 Subject: [PATCH 084/181] docs(template): Replace broken link and instruct people how to find extension repo maintainer [skip ci] --- .github/ISSUE_TEMPLATE/issue_report.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/issue_report.yml b/.github/ISSUE_TEMPLATE/issue_report.yml index d313d377e4..6e2871faa8 100644 --- a/.github/ISSUE_TEMPLATE/issue_report.yml +++ b/.github/ISSUE_TEMPLATE/issue_report.yml @@ -94,11 +94,11 @@ body: required: true - label: I have written a short but informative title. required: true - - label: If this is an issue with an extension, or a request for an extension, I should be contacting the extensions repository's maintainer/support for help. + - label: If this is an issue with an extension, or a request for an extension, I should be contacting the extensions repository's maintainer/support for help ([Source → Extensions → Find the extension → Settings → Tap the `[<>]` icon](https://cdn.aap.my.id/extension-repo-link.png), it *should* redirect you to the maintainer). required: true - - label: I am reporting an issue exclusive to this fork. I have also checked that is not an issue on the [main version of Mihon](https://github.com/mihonapp/mihon) + - label: I am reporting an issue exclusive to this fork. I have also checked that is not an issue on the [main version of Mihon](https://github.com/mihonapp/mihon). required: true - - label: I have tried the [troubleshooting guide](https://mihon.app/help/). + - label: I have tried the [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/). required: true - label: I have updated the app to version **[1.9.7](https://github.com/null2264/yokai/releases/latest)**. required: true From 6614bd3ed88b585cd4919cb58cb34fc3d53a84a4 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 7 Jan 2025 08:23:02 +0700 Subject: [PATCH 085/181] docs(template): Wrong name [skip ci] --- .github/ISSUE_TEMPLATE/issue_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/issue_report.yml b/.github/ISSUE_TEMPLATE/issue_report.yml index 6e2871faa8..38e96918aa 100644 --- a/.github/ISSUE_TEMPLATE/issue_report.yml +++ b/.github/ISSUE_TEMPLATE/issue_report.yml @@ -94,7 +94,7 @@ body: required: true - label: I have written a short but informative title. required: true - - label: If this is an issue with an extension, or a request for an extension, I should be contacting the extensions repository's maintainer/support for help ([Source → Extensions → Find the extension → Settings → Tap the `[<>]` icon](https://cdn.aap.my.id/extension-repo-link.png), it *should* redirect you to the maintainer). + - label: If this is an issue with an extension, or a request for an extension, I should be contacting the extensions repository's maintainer/support for help ([Browse → Extensions → Find the extension → Settings → Tap the `[<>]` icon](https://cdn.aap.my.id/extension-repo-link.png), it *should* redirect you to the maintainer). required: true - label: I am reporting an issue exclusive to this fork. I have also checked that is not an issue on the [main version of Mihon](https://github.com/mihonapp/mihon). required: true From 7d9c0faf8630ed2c3b81a762e2dccb8d112769b2 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 7 Jan 2025 10:08:40 +0700 Subject: [PATCH 086/181] fix: Compose library is not rendering anything --- .../tachiyomi/ui/library/LibraryComposeController.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt index a67ecad114..5a428f5eeb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt @@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.ui.library import android.os.Bundle import androidx.compose.runtime.Composable +import cafe.adriel.voyager.navigator.Navigator +import cafe.adriel.voyager.transitions.CrossfadeTransition import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController import uy.kohesive.injekt.Injekt @@ -18,6 +20,11 @@ class LibraryComposeController( @Composable override fun ScreenContent() { - LibraryScreen() + Navigator( + screen = LibraryScreen(), + content = { + CrossfadeTransition(navigator = it) + }, + ) } } From 48c2ad9b336d6ad7a3e53fa2f4c56129a4b1aba8 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 7 Jan 2025 18:59:08 +0700 Subject: [PATCH 087/181] refactor(library/compose): StateCoroutinePresenter --- .../base/presenter/StateCoroutinePresenter.kt | 15 ++++++ .../ui/library/LibraryComposeController.kt | 30 ------------ .../compose/LibraryComposeController.kt | 49 +++++++++++++++++++ .../compose/LibraryComposePresenter.kt | 11 +++++ .../kanade/tachiyomi/ui/main/MainActivity.kt | 2 +- .../presentation/library/LibraryContent.kt | 31 ++++++++++++ .../presentation/library/LibraryScreen.kt | 26 ++-------- .../main/res/layout/library_controller.xml | 6 +++ 8 files changed, 116 insertions(+), 54 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/StateCoroutinePresenter.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposePresenter.kt create mode 100644 app/src/main/java/yokai/presentation/library/LibraryContent.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/StateCoroutinePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/StateCoroutinePresenter.kt new file mode 100644 index 0000000000..e89ed20283 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/presenter/StateCoroutinePresenter.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.ui.base.presenter + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** + * Presenter that mimic [cafe.adriel.voyager.core.model.StateScreenModel] for easier migration. + * Temporary class while we're migrating to Compose. + */ +abstract class StateCoroutinePresenter(initialState: S) : BaseCoroutinePresenter() { + + protected val mutableState: MutableStateFlow = MutableStateFlow(initialState) + val state: StateFlow = mutableState.asStateFlow() +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt deleted file mode 100644 index 5a428f5eeb..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComposeController.kt +++ /dev/null @@ -1,30 +0,0 @@ -package eu.kanade.tachiyomi.ui.library - -import android.os.Bundle -import androidx.compose.runtime.Composable -import cafe.adriel.voyager.navigator.Navigator -import cafe.adriel.voyager.transitions.CrossfadeTransition -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.ui.base.controller.BaseComposeController -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import yokai.domain.ui.UiPreferences -import yokai.presentation.library.LibraryScreen - -class LibraryComposeController( - bundle: Bundle? = null, - val uiPreferences: UiPreferences = Injekt.get(), - val preferences: PreferencesHelper = Injekt.get(), -) : BaseComposeController(bundle) { - override val shouldHideLegacyAppBar = false - - @Composable - override fun ScreenContent() { - Navigator( - screen = LibraryScreen(), - content = { - CrossfadeTransition(navigator = it) - }, - ) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt new file mode 100644 index 0000000000..f0d3679975 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt @@ -0,0 +1,49 @@ +package eu.kanade.tachiyomi.ui.library.compose + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.core.view.isGone +import androidx.core.view.isVisible +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.databinding.LibraryControllerBinding +import eu.kanade.tachiyomi.ui.base.controller.BaseCoroutineController +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import yokai.domain.ui.UiPreferences +import yokai.presentation.library.LibraryContent +import yokai.presentation.theme.YokaiTheme + +class LibraryComposeController( + bundle: Bundle? = null, + val uiPreferences: UiPreferences = Injekt.get(), + val preferences: PreferencesHelper = Injekt.get(), +) : BaseCoroutineController(bundle) { + + override val presenter = LibraryComposePresenter() + + override fun createBinding(inflater: LayoutInflater) = LibraryControllerBinding.inflate(inflater) + + override fun onViewCreated(view: View) { + super.onViewCreated(view) + binding.composeView.isVisible = true + binding.swipeRefresh.isGone = true + + binding.composeView.setContent { + YokaiTheme { + ScreenContent() + } + } + } + + @Composable + fun ScreenContent() { + val state by presenter.state.collectAsState() + LibraryContent( + columns = 3, + ) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposePresenter.kt new file mode 100644 index 0000000000..d90494adec --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposePresenter.kt @@ -0,0 +1,11 @@ +package eu.kanade.tachiyomi.ui.library.compose + +import eu.kanade.tachiyomi.ui.base.presenter.StateCoroutinePresenter + +class LibraryComposePresenter : + StateCoroutinePresenter(State.Loading) { + + sealed interface State { + data object Loading : State + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index bed1662cda..c68776b631 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -85,8 +85,8 @@ import eu.kanade.tachiyomi.ui.base.SmallToolbarInterface import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.controller.BaseLegacyController import eu.kanade.tachiyomi.ui.base.controller.DialogController -import eu.kanade.tachiyomi.ui.library.LibraryComposeController import eu.kanade.tachiyomi.ui.library.LibraryController +import eu.kanade.tachiyomi.ui.library.compose.LibraryComposeController import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.more.AboutController import eu.kanade.tachiyomi.ui.more.OverflowDialog diff --git a/app/src/main/java/yokai/presentation/library/LibraryContent.kt b/app/src/main/java/yokai/presentation/library/LibraryContent.kt new file mode 100644 index 0000000000..91ecde98a1 --- /dev/null +++ b/app/src/main/java/yokai/presentation/library/LibraryContent.kt @@ -0,0 +1,31 @@ +package yokai.presentation.library + +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import yokai.presentation.AppBarType +import yokai.presentation.YokaiScaffold +import yokai.presentation.library.components.LazyLibraryGrid + +@Composable +fun LibraryContent( + columns: Int, +) { + val items = (0..100).toList() + YokaiScaffold( + onNavigationIconClicked = {}, + appBarType = AppBarType.NONE, + ) { contentPadding -> + LazyLibraryGrid( + columns = columns, + contentPadding = contentPadding, + ) { + items( + items = items, + contentType = { "library_grid_item" } + ) { + Text("Hello world! ($it)") + } + } + } +} diff --git a/app/src/main/java/yokai/presentation/library/LibraryScreen.kt b/app/src/main/java/yokai/presentation/library/LibraryScreen.kt index 4e1abf5fad..a5c66760bf 100644 --- a/app/src/main/java/yokai/presentation/library/LibraryScreen.kt +++ b/app/src/main/java/yokai/presentation/library/LibraryScreen.kt @@ -1,33 +1,13 @@ package yokai.presentation.library -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import yokai.presentation.AppBarType -import yokai.presentation.YokaiScaffold -import yokai.presentation.library.components.LazyLibraryGrid import yokai.util.Screen class LibraryScreen : Screen() { @Composable override fun Content() { - val columns = 3 // FIXME: Retrieve from preferences - val items = (0..100).toList() - YokaiScaffold( - onNavigationIconClicked = {}, - appBarType = AppBarType.NONE, - ) { contentPadding -> - LazyLibraryGrid( - columns = columns, - contentPadding = contentPadding, - ) { - items( - items = items, - contentType = { "library_grid_item" } - ) { - Text("Hello world! ($it)") - } - } - } + LibraryContent( + columns = 3, // FIXME: Retrieve from preferences + ) } } diff --git a/app/src/main/res/layout/library_controller.xml b/app/src/main/res/layout/library_controller.xml index 488491a971..59b7870d12 100644 --- a/app/src/main/res/layout/library_controller.xml +++ b/app/src/main/res/layout/library_controller.xml @@ -15,6 +15,12 @@ android:layout_height="75dp" android:layout_gravity="center" /> + + Date: Tue, 7 Jan 2025 19:05:51 +0700 Subject: [PATCH 088/181] fix(library/compose): Don't left the title empty --- .../compose/LibraryComposeController.kt | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt index f0d3679975..8b1c7e43c8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt @@ -11,17 +11,40 @@ import androidx.core.view.isVisible import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.databinding.LibraryControllerBinding import eu.kanade.tachiyomi.ui.base.controller.BaseCoroutineController +import eu.kanade.tachiyomi.ui.main.BottomSheetController +import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface +import eu.kanade.tachiyomi.ui.main.RootSearchInterface +import java.util.Locale import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import yokai.domain.ui.UiPreferences +import yokai.i18n.MR import yokai.presentation.library.LibraryContent import yokai.presentation.theme.YokaiTheme +import yokai.util.lang.getString class LibraryComposeController( bundle: Bundle? = null, val uiPreferences: UiPreferences = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(), -) : BaseCoroutineController(bundle) { +) : BaseCoroutineController(bundle) , + BottomSheetController, + RootSearchInterface, + FloatingSearchInterface { + + override fun getTitle(): String? { + return view?.context?.getString(MR.strings.library) + } + + override fun getSearchTitle(): String? { + return searchTitle( + if (preferences.showLibrarySearchSuggestions().get() && preferences.librarySearchSuggestion().get().isNotBlank()) { + "\"${preferences.librarySearchSuggestion().get()}\"" + } else { + view?.context?.getString(MR.strings.your_library)?.lowercase(Locale.ROOT) + }, + ) + } override val presenter = LibraryComposePresenter() @@ -46,4 +69,13 @@ class LibraryComposeController( columns = 3, ) } + + override fun showSheet() { + } + + override fun hideSheet() { + } + + override fun toggleSheet() { + } } From f01ace94be11900684242659d069e7de5958822b Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 8 Jan 2025 05:54:27 +0700 Subject: [PATCH 089/181] chore(library/compose): LibraryItem data classes --- .../library/compose/LibraryComposeController.kt | 13 +++++++++++-- .../library/compose/LibraryComposePresenter.kt | 13 +++++++++---- .../tachiyomi/ui/library/models/LibraryItem.kt | 15 +++++++++++++++ .../presentation/library/LibraryContent.kt | 17 ++++++++++++++--- .../yokai/presentation/library/LibraryScreen.kt | 13 ------------- 5 files changed, 49 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/models/LibraryItem.kt delete mode 100644 app/src/main/java/yokai/presentation/library/LibraryScreen.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt index 8b1c7e43c8..7d426f6ad9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt @@ -11,6 +11,7 @@ import androidx.core.view.isVisible import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.databinding.LibraryControllerBinding import eu.kanade.tachiyomi.ui.base.controller.BaseCoroutineController +import eu.kanade.tachiyomi.ui.library.models.LibraryItem import eu.kanade.tachiyomi.ui.main.BottomSheetController import eu.kanade.tachiyomi.ui.main.FloatingSearchInterface import eu.kanade.tachiyomi.ui.main.RootSearchInterface @@ -37,9 +38,11 @@ class LibraryComposeController( } override fun getSearchTitle(): String? { + val searchSuggestion by lazy { preferences.librarySearchSuggestion().get() } + return searchTitle( - if (preferences.showLibrarySearchSuggestions().get() && preferences.librarySearchSuggestion().get().isNotBlank()) { - "\"${preferences.librarySearchSuggestion().get()}\"" + if (preferences.showLibrarySearchSuggestions().get() && searchSuggestion.isNotBlank()) { + "\"$searchSuggestion\"" } else { view?.context?.getString(MR.strings.your_library)?.lowercase(Locale.ROOT) }, @@ -52,8 +55,10 @@ class LibraryComposeController( override fun onViewCreated(view: View) { super.onViewCreated(view) + binding.composeView.isVisible = true binding.swipeRefresh.isGone = true + binding.fastScroller.isGone = true binding.composeView.setContent { YokaiTheme { @@ -66,6 +71,10 @@ class LibraryComposeController( fun ScreenContent() { val state by presenter.state.collectAsState() LibraryContent( + items = listOf( + LibraryItem.Blank(69), + LibraryItem.Blank(420), + ), columns = 3, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposePresenter.kt index d90494adec..03461eeee5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposePresenter.kt @@ -1,11 +1,16 @@ package eu.kanade.tachiyomi.ui.library.compose +import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.ui.base.presenter.StateCoroutinePresenter +import eu.kanade.tachiyomi.ui.library.models.LibraryItem + +typealias LibraryMap = Map> class LibraryComposePresenter : - StateCoroutinePresenter(State.Loading) { + StateCoroutinePresenter(State()) { - sealed interface State { - data object Loading : State - } + data class State( + var isLoading: Boolean = true, + var library: LibraryMap = emptyMap() + ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/models/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/models/LibraryItem.kt new file mode 100644 index 0000000000..a952dae416 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/models/LibraryItem.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.ui.library.models + +import eu.kanade.tachiyomi.data.database.models.LibraryManga + +sealed interface LibraryItem { + data class Blank(val mangaCount: Int = 0) : LibraryItem + data class Hidden(val title: String, val hiddenItems: List) : LibraryItem + data class Manga( + val libraryManga: LibraryManga, + val isLocal: Boolean = false, + val downloadCount: Long = -1, + val unreadCount: Long = -1, + val language: String = "", + ) : LibraryItem +} diff --git a/app/src/main/java/yokai/presentation/library/LibraryContent.kt b/app/src/main/java/yokai/presentation/library/LibraryContent.kt index 91ecde98a1..66fe35dd26 100644 --- a/app/src/main/java/yokai/presentation/library/LibraryContent.kt +++ b/app/src/main/java/yokai/presentation/library/LibraryContent.kt @@ -3,15 +3,16 @@ package yokai.presentation.library import androidx.compose.foundation.lazy.grid.items import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import eu.kanade.tachiyomi.ui.library.models.LibraryItem import yokai.presentation.AppBarType import yokai.presentation.YokaiScaffold import yokai.presentation.library.components.LazyLibraryGrid @Composable fun LibraryContent( + items: List, columns: Int, ) { - val items = (0..100).toList() YokaiScaffold( onNavigationIconClicked = {}, appBarType = AppBarType.NONE, @@ -23,8 +24,18 @@ fun LibraryContent( items( items = items, contentType = { "library_grid_item" } - ) { - Text("Hello world! ($it)") + ) { item -> + when (item) { + is LibraryItem.Blank -> { + Text("Blank: ${item.mangaCount}") + } + is LibraryItem.Hidden -> { + Text("Hidden: ${item.title} - ${item.hiddenItems.size}") + } + is LibraryItem.Manga -> { + Text("Manga: ${item.libraryManga.manga.title}") + } + } } } } diff --git a/app/src/main/java/yokai/presentation/library/LibraryScreen.kt b/app/src/main/java/yokai/presentation/library/LibraryScreen.kt deleted file mode 100644 index a5c66760bf..0000000000 --- a/app/src/main/java/yokai/presentation/library/LibraryScreen.kt +++ /dev/null @@ -1,13 +0,0 @@ -package yokai.presentation.library - -import androidx.compose.runtime.Composable -import yokai.util.Screen - -class LibraryScreen : Screen() { - @Composable - override fun Content() { - LibraryContent( - columns = 3, // FIXME: Retrieve from preferences - ) - } -} From 9cf1fbb1180772ce652a7fcba5171e4be99d11fe Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 8 Jan 2025 07:55:00 +0700 Subject: [PATCH 090/181] fix(i18n): Use behavior instead of behaviour The base should use US English not British English... I'm too used to British English :^) --- i18n/src/commonMain/moko-resources/base/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 7b5c85b035..7198556527 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -295,7 +295,7 @@ Clear history Show download queue Open last read chapter - Long tap Recents behaviour + Long tap Recents behavior Search filters @@ -318,7 +318,7 @@ Check website in WebView Open extensions / migration menu Open global search - Long tap Browse behaviour + Long tap Browse behavior From c9a90f684742be261ca3cfe5247b57caf26c2f97 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 9 Jan 2025 07:22:21 +0700 Subject: [PATCH 091/181] chore(library/compose): View<->Compose Interop REF: https://developer.android.com/develop/ui/compose/touch-input/pointer-input/scroll#parent-view-child-compose --- .../ui/library/compose/LibraryComposeController.kt | 11 +++++++---- .../java/yokai/presentation/library/LibraryContent.kt | 3 +++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt index 7d426f6ad9..ac75e11f02 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/compose/LibraryComposeController.kt @@ -6,6 +6,9 @@ import android.view.View import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.core.view.isGone import androidx.core.view.isVisible import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -69,12 +72,12 @@ class LibraryComposeController( @Composable fun ScreenContent() { + val nestedScrollInterop = rememberNestedScrollInteropConnection() + val state by presenter.state.collectAsState() LibraryContent( - items = listOf( - LibraryItem.Blank(69), - LibraryItem.Blank(420), - ), + modifier = Modifier.nestedScroll(nestedScrollInterop), + items = (0..50).map { LibraryItem.Blank(it) }, columns = 3, ) } diff --git a/app/src/main/java/yokai/presentation/library/LibraryContent.kt b/app/src/main/java/yokai/presentation/library/LibraryContent.kt index 66fe35dd26..b334a2e25c 100644 --- a/app/src/main/java/yokai/presentation/library/LibraryContent.kt +++ b/app/src/main/java/yokai/presentation/library/LibraryContent.kt @@ -3,6 +3,7 @@ package yokai.presentation.library import androidx.compose.foundation.lazy.grid.items import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import eu.kanade.tachiyomi.ui.library.models.LibraryItem import yokai.presentation.AppBarType import yokai.presentation.YokaiScaffold @@ -10,6 +11,7 @@ import yokai.presentation.library.components.LazyLibraryGrid @Composable fun LibraryContent( + modifier: Modifier = Modifier, items: List, columns: Int, ) { @@ -18,6 +20,7 @@ fun LibraryContent( appBarType = AppBarType.NONE, ) { contentPadding -> LazyLibraryGrid( + modifier = modifier, columns = columns, contentPadding = contentPadding, ) { From c6da3325b3c1391ed54346a45cebc73472e98081 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 10 Jan 2025 11:06:11 +0700 Subject: [PATCH 092/181] fix(AppBar): Wrap scroll behaviour with remember { } --- .../java/yokai/presentation/core/AppBar.kt | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt index 35932b5fbd..43c5444245 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -30,6 +30,7 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarState import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.runtime.remember import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.SideEffect @@ -546,22 +547,21 @@ fun enterAlwaysCollapsedScrollBehavior( snapAnimationSpec: AnimationSpec? = spring(stiffness = Spring.StiffnessMediumLow), flingAnimationSpec: DecayAnimationSpec? = rememberSplineBasedDecay() ): TopAppBarScrollBehavior { - val topHeightPx: Float - val totalHeightPx: Float - LocalDensity.current.run { - topHeightPx = CollapsedContainerHeight.toPx() - totalHeightPx = ExpandedContainerHeight.toPx() - } + return remember(state, canScroll, isAtTop, snapAnimationSpec, flingAnimationSpec) { + val (topHeightPx, totalHeightPx) = with(LocalDensity.current) { + CollapsedContainerHeight.toPx() to ExpandedContainerHeight.toPx() + } - return EnterAlwaysCollapsedScrollBehavior( - state = state, - snapAnimationSpec = snapAnimationSpec, - flingAnimationSpec = flingAnimationSpec, - canScroll = canScroll, - isAtTop = isAtTop, - topHeightPx = topHeightPx, - totalHeightPx = totalHeightPx, - ) + EnterAlwaysCollapsedScrollBehavior( + state = state, + snapAnimationSpec = snapAnimationSpec, + flingAnimationSpec = flingAnimationSpec, + canScroll = canScroll, + isAtTop = isAtTop, + topHeightPx = topHeightPx, + totalHeightPx = totalHeightPx, + ) + } } private class EnterAlwaysCollapsedScrollBehavior( From 258708b0389150048c98e2381b82e2baa8c93669 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 10 Jan 2025 11:10:28 +0700 Subject: [PATCH 093/181] fix: Fix build --- .../core/src/main/java/yokai/presentation/core/AppBar.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt index 43c5444245..0dcaf44d35 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -547,11 +547,11 @@ fun enterAlwaysCollapsedScrollBehavior( snapAnimationSpec: AnimationSpec? = spring(stiffness = Spring.StiffnessMediumLow), flingAnimationSpec: DecayAnimationSpec? = rememberSplineBasedDecay() ): TopAppBarScrollBehavior { - return remember(state, canScroll, isAtTop, snapAnimationSpec, flingAnimationSpec) { - val (topHeightPx, totalHeightPx) = with(LocalDensity.current) { - CollapsedContainerHeight.toPx() to ExpandedContainerHeight.toPx() - } + val (topHeightPx, totalHeightPx) = with(LocalDensity.current) { + CollapsedContainerHeight.toPx() to ExpandedContainerHeight.toPx() + } + return remember(state, canScroll, isAtTop, snapAnimationSpec, flingAnimationSpec, topHeightPx, totalHeightPx) { EnterAlwaysCollapsedScrollBehavior( state = state, snapAnimationSpec = snapAnimationSpec, From f37e657a9b9a2133a925dbd30e2a80506df011a7 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 13 Jan 2025 11:29:07 +0700 Subject: [PATCH 094/181] fix: Prevent lateinit crash --- .../java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index 9b288c450c..7ec6ed9ced 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -806,6 +806,8 @@ class MangaDetailsController : } private fun getHeader(): MangaHeaderHolder? { + if (isBindingInitialized) return null + return if (isTablet) { binding.tabletRecycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder } else { From 453ea32bc956352b9134ebbbade42ac4a15928e7 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 13 Jan 2025 11:32:49 +0700 Subject: [PATCH 095/181] chore: Hide compose library on prod build --- .../controllers/SettingsAdvancedController.kt | 63 ++++++++++--------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt index c1e7143314..4e314f21de 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt @@ -394,9 +394,11 @@ class SettingsAdvancedController : SettingsLegacyController() { onClick { LibraryUpdateJob.startNow(context, target = Target.TRACKING) } } - switchPreference { - bindTo(basePreferences.composeLibrary()) - title = context.getString(MR.strings.pref_use_compose_library).addBetaTag(context) + if (BuildConfig.FLAVOR == "dev" || BuildConfig.DEBUG || BuildConfig.NIGHTLY) { + switchPreference { + bindTo(basePreferences.composeLibrary()) + title = context.getString(MR.strings.pref_use_compose_library).addBetaTag(context) + } } } @@ -457,36 +459,37 @@ class SettingsAdvancedController : SettingsLegacyController() { } } - preferenceCategory { - title = "Danger zone!" - isVisible = BuildConfig.FLAVOR == "dev" || BuildConfig.DEBUG || BuildConfig.NIGHTLY + if (BuildConfig.FLAVOR == "dev" || BuildConfig.DEBUG || BuildConfig.NIGHTLY) { + preferenceCategory { + title = "Danger zone!" - preference { - title = "Crash the app!" - summary = "To test crashes" - onClick { - activity!!.materialAlertDialog() - .setTitle(MR.strings.warning) - .setMessage("I told you this would crash the app, why would you want that?") - .setPositiveButton("Crash it anyway") { _, _ -> throw RuntimeException("Fell into the void") } - .setNegativeButton("Nevermind", null) - .show() + preference { + title = "Crash the app!" + summary = "To test crashes" + onClick { + activity!!.materialAlertDialog() + .setTitle(MR.strings.warning) + .setMessage("I told you this would crash the app, why would you want that?") + .setPositiveButton("Crash it anyway") { _, _ -> throw RuntimeException("Fell into the void") } + .setNegativeButton("Nevermind", null) + .show() + } } - } - preference { - title = "Prune finished workers" - summary = "In case worker stuck in FAILED state and you're too impatient to wait" - onClick { - activity!!.materialAlertDialog() - .setTitle("Are you sure?") - .setMessage( - "Failed workers should clear out by itself eventually, " + - "this option should only be used if you're being impatient and you know what you're doing." - ) - .setPositiveButton("Prune") { _, _ -> context.workManager.pruneWork() } - .setNegativeButton("Cancel", null) - .show() + preference { + title = "Prune finished workers" + summary = "In case worker stuck in FAILED state and you're too impatient to wait" + onClick { + activity!!.materialAlertDialog() + .setTitle("Are you sure?") + .setMessage( + "Failed workers should clear out by itself eventually, " + + "this option should only be used if you're being impatient and you know what you're doing." + ) + .setPositiveButton("Prune") { _, _ -> context.workManager.pruneWork() } + .setNegativeButton("Cancel", null) + .show() + } } } } From baaa84127898ffcd02ffe86d21eeb4d518592923 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 13 Jan 2025 12:23:18 +0700 Subject: [PATCH 096/181] fix: Explicitly disable stable ids --- .../kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt index e27e2eb2b7..b95f48ed40 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt @@ -179,6 +179,7 @@ open class BrowseSourceController(bundle: Bundle) : // Initialize adapter, scroll listener and recycler views adapter = FlexibleAdapter(null, this) + adapter.setHasStableIds(false) setupRecycler(view) binding.fab.isVisible = presenter.sourceFilters.isNotEmpty() From 33fa77d527529f109deab9d84c6ab37623005891 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 13 Jan 2025 12:39:18 +0700 Subject: [PATCH 097/181] fix: Fix some NPE crashes --- .../java/eu/kanade/tachiyomi/ui/library/LibraryController.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 9f49ec22f5..00d07100e6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -1375,7 +1375,7 @@ open class LibraryController( setActiveCategory() return } - val headerPosition = adapter.indexOf(pos) + val headerPosition = mAdapter?.indexOf(pos) ?: return if (headerPosition > -1) { val activityBinding = activityBinding ?: return val index = adapter.headerItems.indexOf(adapter.getItem(headerPosition)) @@ -1686,7 +1686,7 @@ open class LibraryController( lastItem = null isDragging = false binding.swipeRefresh.isEnabled = true - if (adapter.selectedItemCount > 0) { + if (mAdapter == null || adapter.selectedItemCount > 0) { lastItemPosition = null return } From 9e5d13f261db14e8f539684a5b4cf4d2baaca642 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 13 Jan 2025 13:22:19 +0700 Subject: [PATCH 098/181] fix: Fix lateinit error --- .../ui/source/globalsearch/GlobalSearchPresenter.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt index b544928f31..4ab9933f65 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt @@ -185,7 +185,7 @@ open class GlobalSearchPresenter( MangasPage(emptyList(), false) } .mangas.take(10) - .map { networkToLocalManga(it, source.id) } + .mapNotNull { networkToLocalManga(it, source.id) } fetchImage(mangas, source) if (mangas.isNotEmpty() && !loadTime.containsKey(source.id)) { loadTime[source.id] = Date().time @@ -275,14 +275,18 @@ open class GlobalSearchPresenter( * @param sManga the manga from the source. * @return a manga from the database. */ - protected open suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { + protected open suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga? { var localManga = getManga.awaitByUrlAndSource(sManga.url, sourceId) if (localManga == null) { + if (!sManga::title.isInitialized) return null + val newManga = Manga.create(sManga.url, sManga.title, sourceId) newManga.copyFrom(sManga) newManga.id = insertManga.await(newManga) localManga = newManga } else if (!localManga.favorite) { + if (!sManga::title.isInitialized) return localManga + // if the manga isn't a favorite, set its display title from source // if it later becomes a favorite, updated title will go to db localManga.title = sManga.title From 915ce2090496ca3c7e348521164dec86298d66f4 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 13 Jan 2025 13:30:11 +0700 Subject: [PATCH 099/181] fix: Fix build --- .../kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt index b95f48ed40..0a1c415392 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt @@ -179,7 +179,7 @@ open class BrowseSourceController(bundle: Bundle) : // Initialize adapter, scroll listener and recycler views adapter = FlexibleAdapter(null, this) - adapter.setHasStableIds(false) + adapter?.setHasStableIds(false) setupRecycler(view) binding.fab.isVisible = presenter.sourceFilters.isNotEmpty() From d6ffbe15ee340efd1975e7b9d52fda47756cb377 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 13 Jan 2025 13:46:29 +0700 Subject: [PATCH 100/181] fix: Title is only lateinit on SMangaImpl --- .../globalsearch/GlobalSearchPresenter.kt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt index 4ab9933f65..49537791ce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchPresenter.kt @@ -278,18 +278,24 @@ open class GlobalSearchPresenter( protected open suspend fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga? { var localManga = getManga.awaitByUrlAndSource(sManga.url, sourceId) if (localManga == null) { - if (!sManga::title.isInitialized) return null - - val newManga = Manga.create(sManga.url, sManga.title, sourceId) + val newManga = + try { + Manga.create(sManga.url, sManga.title, sourceId) + } catch (_: UninitializedPropertyAccessException) { + return null + } newManga.copyFrom(sManga) newManga.id = insertManga.await(newManga) localManga = newManga } else if (!localManga.favorite) { - if (!sManga::title.isInitialized) return localManga - // if the manga isn't a favorite, set its display title from source // if it later becomes a favorite, updated title will go to db - localManga.title = sManga.title + localManga.title = + try { + sManga.title + } catch (_: UninitializedPropertyAccessException) { + return localManga + } } return localManga } From 86b01a297fd62960723fc409426119b0a0f6ec29 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 14 Jan 2025 03:59:47 +0700 Subject: [PATCH 101/181] fix: Can't set stable id like that --- .../tachiyomi/ui/source/browse/BrowseSourceController.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt index 0a1c415392..5890885938 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt @@ -178,8 +178,7 @@ open class BrowseSourceController(bundle: Bundle) : super.onViewCreated(view) // Initialize adapter, scroll listener and recycler views - adapter = FlexibleAdapter(null, this) - adapter?.setHasStableIds(false) + adapter = FlexibleAdapter(null, this, false) setupRecycler(view) binding.fab.isVisible = presenter.sourceFilters.isNotEmpty() From 9ed12ef07ce7205060f843581d260f136b5be2da Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 16 Jan 2025 17:50:21 +0700 Subject: [PATCH 102/181] fix: Forgor to put `!` --- .../java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index 7ec6ed9ced..1337881ee6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -806,7 +806,7 @@ class MangaDetailsController : } private fun getHeader(): MangaHeaderHolder? { - if (isBindingInitialized) return null + if (!isBindingInitialized) return null return if (isTablet) { binding.tabletRecycler.findViewHolderForAdapterPosition(0) as? MangaHeaderHolder From 63cdf247b4ae30c8103462ad0677fdb697949af4 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 17 Jan 2025 09:14:35 +0700 Subject: [PATCH 103/181] chore: Sync AppBar code with upstream --- CHANGELOG.md | 1 + gradle/compose.versions.toml | 2 +- .../java/yokai/presentation/core/AppBar.kt | 316 +++++++++++++----- 3 files changed, 230 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f419a2625..bd401f7b76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Refactor Library to store LibraryMap instead of flatten list of LibraryItem - LibraryItem abstraction to make it easier to manage - LibraryManga no longer extend MangaImpl +- Update dependency androidx.compose:compose-bom to v2025.01.00 ## [1.9.7] diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index eb7c047db8..4c62e2e8da 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,5 +1,5 @@ [versions] -compose = "2024.12.01" +compose = "2025.01.00" [libraries] bom = { module = "androidx.compose:compose-bom", version.ref = "compose" } diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt index 0dcaf44d35..8fd872d0cf 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -17,47 +17,59 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.TopAppBarColors import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarState import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.runtime.remember import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.lerp import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.AlignmentLine +import androidx.compose.ui.layout.IntrinsicMeasurable +import androidx.compose.ui.layout.IntrinsicMeasureScope import androidx.compose.ui.layout.LastBaseline import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasurePolicy +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.isTraversalGroup +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.isFinite import androidx.compose.ui.unit.isSpecified import androidx.compose.ui.util.fastCoerceIn import androidx.compose.ui.util.fastFirst +import androidx.compose.ui.util.fastMaxOfOrNull +import androidx.compose.ui.util.fastSumBy import kotlin.math.abs import kotlin.math.max import kotlin.math.roundToInt @@ -92,6 +104,10 @@ fun ExpandedAppBar( windowInsets = windowInsets, colors = colors, scrollBehavior = scrollBehavior, + subtitle = null, + subtitleTextStyle = TextStyle.Default, + smallSubtitle = null, + smallSubtitleTextStyle = TextStyle.Default, ) } @@ -103,6 +119,10 @@ private fun TwoRowsTopAppBar( titleBottomPadding: Dp, smallTitle: @Composable () -> Unit, smallTitleTextStyle: TextStyle, + subtitle: (@Composable () -> Unit)?, + subtitleTextStyle: TextStyle, + smallSubtitle: (@Composable () -> Unit)?, + smallSubtitleTextStyle: TextStyle, navigationIcon: @Composable () -> Unit, actions: @Composable RowScope.() -> Unit, collapsedHeight: Dp, @@ -143,13 +163,13 @@ private fun TwoRowsTopAppBar( // This will potentially animate or interpolate a transition between the container color and the // container's scrolled color according to the app bar's scroll state. val colorTransitionFraction = scrollBehavior?.state?.bottomCollapsedFraction(collapsedHeightPx, expandedHeightPx) ?: 0f - - val appBarContainerColor = + val appBarContainerColor = { lerp( colors.containerColor, colors.scrolledContainerColor, FastOutLinearInEasing.transform(colorTransitionFraction) ) + } // Wrap the given actions in a Row. val actionsRow = @@ -167,15 +187,19 @@ private fun TwoRowsTopAppBar( val hideTopRowSemantics = colorTransitionFraction < 0.5f val hideBottomRowSemantics = !hideTopRowSemantics - Surface(modifier = modifier, color = appBarContainerColor) { + Box( + modifier = + modifier + .drawBehind { drawRect(color = appBarContainerColor()) } + .semantics { isTraversalGroup = true } + .pointerInput(Unit) {} + ) { Column { AppBarLayout( modifier = - Modifier - .windowInsetsPadding(windowInsets) + Modifier.windowInsetsPadding(windowInsets) // clip after padding so we don't show the title over the inset area - .clipToBounds() - .heightIn(max = collapsedHeight), + .clipToBounds(), scrolledOffset = { scrollBehavior?.state?.topHeightOffset( topHeightPx = collapsedHeightPx, @@ -184,16 +208,21 @@ private fun TwoRowsTopAppBar( }, navigationIconContentColor = colors.navigationIconContentColor, titleContentColor = colors.titleContentColor, + //subtitleContentColor = colors.subtitleContentColor, + subtitleContentColor = colors.titleContentColor, actionIconContentColor = colors.actionIconContentColor, title = smallTitle, titleTextStyle = smallTitleTextStyle, - titleAlpha = topTitleAlpha, + titleAlpha = { topTitleAlpha }, titleVerticalArrangement = Arrangement.Bottom, - titleHorizontalArrangement = Arrangement.Start, + titleHorizontalAlignment = Alignment.Start, titleBottomPadding = 0, + subtitle = smallSubtitle, + subtitleTextStyle = smallSubtitleTextStyle, hideTitleSemantics = hideTopRowSemantics, navigationIcon = navigationIcon, actions = actionsRow, + height = collapsedHeight, ) AppBarLayout( modifier = @@ -202,8 +231,7 @@ private fun TwoRowsTopAppBar( // top // padding will always be applied by the layout above .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal)) - .clipToBounds() - .heightIn(max = expandedHeight - collapsedHeight), + .clipToBounds(), scrolledOffset = { scrollBehavior?.state?.bottomHeightOffset( topHeightPx = collapsedHeightPx, @@ -212,64 +240,105 @@ private fun TwoRowsTopAppBar( }, navigationIconContentColor = colors.navigationIconContentColor, titleContentColor = colors.titleContentColor, + //subtitleContentColor = colors.subtitleContentColor, + subtitleContentColor = colors.titleContentColor, actionIconContentColor = colors.actionIconContentColor, title = title, titleTextStyle = titleTextStyle, - titleAlpha = bottomTitleAlpha, + titleAlpha = { bottomTitleAlpha }, titleVerticalArrangement = Arrangement.Bottom, - titleHorizontalArrangement = Arrangement.Start, + titleHorizontalAlignment = Alignment.Start, titleBottomPadding = titleBottomPaddingPx, hideTitleSemantics = hideBottomRowSemantics, + subtitle = subtitle, + subtitleTextStyle = subtitleTextStyle, navigationIcon = {}, - actions = {} + actions = {}, + height = expandedHeight - collapsedHeight, ) } } } +/** + * Alternative to `() -> Float` but avoids boxing. + */ +internal fun interface FloatProducer { + /** Returns the Float. */ + operator fun invoke(): Float +} + @Composable private fun AppBarLayout( modifier: Modifier, - scrolledOffset: ScrolledOffset, + scrolledOffset: FloatProducer, navigationIconContentColor: Color, titleContentColor: Color, + subtitleContentColor: Color, actionIconContentColor: Color, title: @Composable () -> Unit, titleTextStyle: TextStyle, - titleAlpha: Float, + subtitle: (@Composable () -> Unit)?, + subtitleTextStyle: TextStyle, + titleAlpha: () -> Float, titleVerticalArrangement: Arrangement.Vertical, - titleHorizontalArrangement: Arrangement.Horizontal, + titleHorizontalAlignment: Alignment.Horizontal, titleBottomPadding: Int, hideTitleSemantics: Boolean, navigationIcon: @Composable () -> Unit, actions: @Composable () -> Unit, + height: Dp, ) { Layout( { - Box(Modifier - .layoutId("navigationIcon") - .padding(start = TopAppBarHorizontalPadding)) { + Box(Modifier.layoutId("navigationIcon").padding(start = TopAppBarHorizontalPadding)) { CompositionLocalProvider( LocalContentColor provides navigationIconContentColor, content = navigationIcon ) } - Box( - Modifier - .layoutId("title") - .padding(horizontal = TopAppBarHorizontalPadding) - .then(if (hideTitleSemantics) Modifier.clearAndSetSemantics {} else Modifier) - .graphicsLayer(alpha = titleAlpha) - ) { - ProvideContentColorTextStyle( - contentColor = titleContentColor, - textStyle = titleTextStyle, - content = title - ) + if (subtitle != null) { + Column( + modifier = + Modifier.layoutId("title") + .padding(horizontal = TopAppBarHorizontalPadding) + .then( + if (hideTitleSemantics) Modifier.clearAndSetSemantics {} + else Modifier + ) + .graphicsLayer { alpha = titleAlpha() }, + horizontalAlignment = titleHorizontalAlignment + ) { + ProvideContentColorTextStyle( + contentColor = titleContentColor, + textStyle = titleTextStyle, + content = title + ) + ProvideContentColorTextStyle( + contentColor = subtitleContentColor, + textStyle = subtitleTextStyle, + content = subtitle + ) + } + } else { // TODO(b/352770398): Workaround to maintain compatibility + Box( + modifier = + Modifier.layoutId("title") + .padding(horizontal = TopAppBarHorizontalPadding) + .then( + if (hideTitleSemantics) Modifier.clearAndSetSemantics {} + else Modifier + ) + .graphicsLayer { alpha = titleAlpha() } + ) { + ProvideContentColorTextStyle( + contentColor = titleContentColor, + textStyle = titleTextStyle, + content = title + ) + } } - Box(Modifier - .layoutId("actionIcons") - .padding(end = TopAppBarHorizontalPadding)) { + Box(Modifier.layoutId("actionIcons").padding(end = TopAppBarHorizontalPadding)) { CompositionLocalProvider( LocalContentColor provides actionIconContentColor, content = actions @@ -277,7 +346,36 @@ private fun AppBarLayout( } }, modifier = modifier, - ) { measurables, constraints -> + measurePolicy = + remember( + scrolledOffset, + titleVerticalArrangement, + titleHorizontalAlignment, + titleBottomPadding, + height + ) { + TopAppBarMeasurePolicy( + scrolledOffset, + titleVerticalArrangement, + titleHorizontalAlignment, + titleBottomPadding, + height + ) + } + ) +} + +private class TopAppBarMeasurePolicy( + val scrolledOffset: FloatProducer, + val titleVerticalArrangement: Arrangement.Vertical, + val titleHorizontalAlignment: Alignment.Horizontal, + val titleBottomPadding: Int, + val height: Dp, +) : MeasurePolicy { + override fun MeasureScope.measure( + measurables: List, + constraints: Constraints + ): MeasureResult { val navigationIconPlaceable = measurables .fastFirst { it.layoutId == "navigationIcon" } @@ -309,16 +407,67 @@ private fun AppBarLayout( // Subtract the scrolledOffset from the maxHeight. The scrolledOffset is expected to be // equal or smaller than zero. - val scrolledOffsetValue = scrolledOffset.offset() + val scrolledOffsetValue = scrolledOffset() val heightOffset = if (scrolledOffsetValue.isNaN()) 0 else scrolledOffsetValue.roundToInt() + val maxLayoutHeight = max(height.roundToPx(), titlePlaceable.height) val layoutHeight = if (constraints.maxHeight == Constraints.Infinity) { - constraints.maxHeight + maxLayoutHeight } else { - constraints.maxHeight + heightOffset + (maxLayoutHeight + heightOffset).coerceAtLeast(0) } + return placeTopAppBar( + constraints, + layoutHeight, + maxLayoutHeight, + navigationIconPlaceable, + titlePlaceable, + actionIconsPlaceable, + titleBaseline + ) + } + + override fun IntrinsicMeasureScope.minIntrinsicWidth( + measurables: List, + height: Int + ) = measurables.fastSumBy { it.minIntrinsicWidth(height) } + + override fun IntrinsicMeasureScope.minIntrinsicHeight( + measurables: List, + width: Int + ): Int { + return max( + height.roundToPx(), + measurables.fastMaxOfOrNull { it.minIntrinsicHeight(width) } ?: 0 + ) + } + + override fun IntrinsicMeasureScope.maxIntrinsicWidth( + measurables: List, + height: Int + ) = measurables.fastSumBy { it.maxIntrinsicWidth(height) } + + override fun IntrinsicMeasureScope.maxIntrinsicHeight( + measurables: List, + width: Int + ): Int { + return max( + height.roundToPx(), + measurables.fastMaxOfOrNull { it.maxIntrinsicHeight(width) } ?: 0 + ) + } + + private fun MeasureScope.placeTopAppBar( + constraints: Constraints, + layoutHeight: Int, + maxLayoutHeight: Int, + navigationIconPlaceable: Placeable, + titlePlaceable: Placeable, + actionIconsPlaceable: Placeable, + titleBaseline: Int + ): MeasureResult = layout(constraints.maxWidth, layoutHeight) { // Navigation icon navigationIconPlaceable.placeRelative( @@ -343,62 +492,52 @@ private fun AppBarLayout( }, ) - // Title - titlePlaceable.placeRelative( - x = - when (titleHorizontalArrangement) { - Arrangement.Center -> { - var baseX = (constraints.maxWidth - titlePlaceable.width) / 2 - if (baseX < navigationIconPlaceable.width) { - // May happen if the navigation is wider than the actions and the - // title is long. In this case, prioritize showing more of the title - // by - // offsetting it to the right. - baseX += (navigationIconPlaceable.width - baseX) - } else if ( - baseX + titlePlaceable.width > - constraints.maxWidth - actionIconsPlaceable.width - ) { - // May happen if the actions are wider than the navigation and the - // title - // is long. In this case, offset to the left. - baseX += - ((constraints.maxWidth - actionIconsPlaceable.width) - - (baseX + titlePlaceable.width)) - } - baseX - } - Arrangement.End -> - constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width - // Arrangement.Start. - // An TopAppBarTitleInset will make sure the title is offset in case the - // navigation icon is missing. - else -> max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width) - }, - y = + titlePlaceable.let { + val start = max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width) + val end = actionIconsPlaceable.width + + // Align using the maxWidth. We will adjust the position later according to the + // start and end. This is done to ensure that a center alignment is still maintained + // when the start and end have different widths. Note that the title is centered + // relative to the entire app bar width, and not just centered between the + // navigation icon and the actions. + var titleX = + titleHorizontalAlignment.align( + size = titlePlaceable.width, + space = constraints.maxWidth, + // Using Ltr as we call placeRelative later on. + layoutDirection = LayoutDirection.Ltr + ) + // Reposition the title based on the start and the end (i.e. the navigation and + // action widths). + if (titleX < start) { + titleX += (start - titleX) + } else if (titleX + titlePlaceable.width > constraints.maxWidth - end) { + titleX += ((constraints.maxWidth - end) - (titleX + titlePlaceable.width)) + } + + // The titleVerticalArrangement is always one of Center or Bottom. + val titleY = when (titleVerticalArrangement) { Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2 // Apply bottom padding from the title's baseline only when the Arrangement - // is - // "Bottom". + // is "Bottom". Arrangement.Bottom -> { val padding = if (titleBottomPadding == 0) { (constraints.maxHeight - titlePlaceable.height) / 2 } else { - titleBottomPadding + titleBottomPadding - (titlePlaceable.height - titleBaseline) } // Calculate the actual padding from the bottom of the title, taking // into account its baseline. - val paddingFromBottom = - padding - (titlePlaceable.height - titleBaseline) + val paddingFromBottom = padding - (titlePlaceable.height - titleBaseline) + // Adjust the bottom padding to a smaller number if there is no room - // to - // fit the title. + // to fit the title. val heightWithPadding = paddingFromBottom + titlePlaceable.height val adjustedBottomPadding = - if (heightWithPadding > constraints.maxHeight) { - paddingFromBottom - - (heightWithPadding - constraints.maxHeight) + if (heightWithPadding > maxLayoutHeight) { + paddingFromBottom - (heightWithPadding - maxLayoutHeight) } else { paddingFromBottom } @@ -407,8 +546,10 @@ private fun AppBarLayout( } // Arrangement.Top else -> 0 - }, - ) + } + + it.placeRelative(titleX, titleY) + } // Action icons actionIconsPlaceable.placeRelative( @@ -433,7 +574,6 @@ private fun AppBarLayout( }, ) } - } } @Composable From 8b53e5ad102a0a915756004b51c0a58b109e3263 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 17 Jan 2025 09:26:22 +0700 Subject: [PATCH 104/181] fix(AppBar): Adjust title padding --- .../src/main/java/yokai/presentation/core/AppBar.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt index 8fd872d0cf..7582b64429 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -523,14 +523,14 @@ private class TopAppBarMeasurePolicy( // Apply bottom padding from the title's baseline only when the Arrangement // is "Bottom". Arrangement.Bottom -> { - val padding = if (titleBottomPadding == 0) { - (constraints.maxHeight - titlePlaceable.height) / 2 - } else { - titleBottomPadding - (titlePlaceable.height - titleBaseline) - } // Calculate the actual padding from the bottom of the title, taking // into account its baseline. - val paddingFromBottom = padding - (titlePlaceable.height - titleBaseline) + val adjustedTitleHeight = titlePlaceable.height - titleBaseline + val paddingFromBottom = if (titleBottomPadding == 0) { + (constraints.maxHeight - adjustedTitleHeight) / 2 + } else { + titleBottomPadding - adjustedTitleHeight + } // Adjust the bottom padding to a smaller number if there is no room // to fit the title. From d2ddf7dfb010abf46a1bf0fb5d09315467cda255 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 17 Jan 2025 09:35:32 +0700 Subject: [PATCH 105/181] fix(AppBar): Also adjust the other placeables' padding --- .../core/src/main/java/yokai/presentation/core/AppBar.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt index 7582b64429..575512310a 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -475,13 +475,11 @@ private class TopAppBarMeasurePolicy( y = when (titleVerticalArrangement) { Arrangement.Bottom -> { - val padding = (constraints.maxHeight - navigationIconPlaceable.height) / 2 - val paddingFromBottom = padding - (navigationIconPlaceable.height - titleBaseline) + val paddingFromBottom = (constraints.maxHeight - (navigationIconPlaceable.height - titleBaseline)) / 2 val heightWithPadding = paddingFromBottom + navigationIconPlaceable.height val adjustedBottomPadding = if (heightWithPadding > constraints.maxHeight) { - paddingFromBottom - - (heightWithPadding - constraints.maxHeight) + paddingFromBottom - (heightWithPadding - constraints.maxHeight) } else { paddingFromBottom } @@ -557,8 +555,7 @@ private class TopAppBarMeasurePolicy( y = when (titleVerticalArrangement) { Arrangement.Bottom -> { - val padding = (constraints.maxHeight - actionIconsPlaceable.height) / 2 - val paddingFromBottom = padding - (actionIconsPlaceable.height - titleBaseline) + val paddingFromBottom = (constraints.maxHeight - (actionIconsPlaceable.height - titleBaseline)) / 2 val heightWithPadding = paddingFromBottom + actionIconsPlaceable.height val adjustedBottomPadding = if (heightWithPadding > constraints.maxHeight) { From ece849b00821cc333ccbe150a0c364df93e7fcc7 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 17 Jan 2025 10:16:57 +0700 Subject: [PATCH 106/181] fix(AppBar): Use maxLayoutHeight instead of constraints.maxHeight Also revert padding changes --- .../java/yokai/presentation/core/AppBar.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt index 575512310a..6715a6e07b 100644 --- a/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt +++ b/presentation/core/src/main/java/yokai/presentation/core/AppBar.kt @@ -475,11 +475,12 @@ private class TopAppBarMeasurePolicy( y = when (titleVerticalArrangement) { Arrangement.Bottom -> { - val paddingFromBottom = (constraints.maxHeight - (navigationIconPlaceable.height - titleBaseline)) / 2 + val padding = (maxLayoutHeight - navigationIconPlaceable.height) / 2 + val paddingFromBottom = padding - (navigationIconPlaceable.height - titleBaseline) val heightWithPadding = paddingFromBottom + navigationIconPlaceable.height val adjustedBottomPadding = - if (heightWithPadding > constraints.maxHeight) { - paddingFromBottom - (heightWithPadding - constraints.maxHeight) + if (heightWithPadding > maxLayoutHeight) { + paddingFromBottom - (heightWithPadding - maxLayoutHeight) } else { paddingFromBottom } @@ -523,12 +524,11 @@ private class TopAppBarMeasurePolicy( Arrangement.Bottom -> { // Calculate the actual padding from the bottom of the title, taking // into account its baseline. - val adjustedTitleHeight = titlePlaceable.height - titleBaseline val paddingFromBottom = if (titleBottomPadding == 0) { - (constraints.maxHeight - adjustedTitleHeight) / 2 + (maxLayoutHeight - titlePlaceable.height) / 2 } else { - titleBottomPadding - adjustedTitleHeight - } + titleBottomPadding + } - (titlePlaceable.height - titleBaseline) // Adjust the bottom padding to a smaller number if there is no room // to fit the title. @@ -555,12 +555,13 @@ private class TopAppBarMeasurePolicy( y = when (titleVerticalArrangement) { Arrangement.Bottom -> { - val paddingFromBottom = (constraints.maxHeight - (actionIconsPlaceable.height - titleBaseline)) / 2 + val padding = (maxLayoutHeight - actionIconsPlaceable.height) / 2 + val paddingFromBottom = padding - (actionIconsPlaceable.height - titleBaseline) val heightWithPadding = paddingFromBottom + actionIconsPlaceable.height val adjustedBottomPadding = - if (heightWithPadding > constraints.maxHeight) { + if (heightWithPadding > maxLayoutHeight) { paddingFromBottom - - (heightWithPadding - constraints.maxHeight) + (heightWithPadding - maxLayoutHeight) } else { paddingFromBottom } From 4dd8aece0c82fc465d10c5cde53004101217e28b Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 24 Jan 2025 18:54:06 +0700 Subject: [PATCH 107/181] fix: Temporarily hide the experimental compose library switch for nightly --- .../ui/setting/controllers/SettingsAdvancedController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt index 4e314f21de..8724eea63c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt @@ -394,7 +394,7 @@ class SettingsAdvancedController : SettingsLegacyController() { onClick { LibraryUpdateJob.startNow(context, target = Target.TRACKING) } } - if (BuildConfig.FLAVOR == "dev" || BuildConfig.DEBUG || BuildConfig.NIGHTLY) { + if (BuildConfig.FLAVOR == "dev" || BuildConfig.DEBUG) { switchPreference { bindTo(basePreferences.composeLibrary()) title = context.getString(MR.strings.pref_use_compose_library).addBetaTag(context) From 0bf55a8ca0ec241f0ec3b0c08829a53d5ac6aebf Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 2 Feb 2025 07:02:23 +0700 Subject: [PATCH 108/181] ci: Tweak GitHub Actions and switch from Adopt to Temurin JDK --- .github/workflows/build_check.yml | 9 ++++++--- .github/workflows/build_push.yml | 30 +++++++++++++++--------------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build_check.yml b/.github/workflows/build_check.yml index d6b399c6e4..fe3797535f 100644 --- a/.github/workflows/build_check.yml +++ b/.github/workflows/build_check.yml @@ -33,15 +33,18 @@ jobs: uses: null2264/actions/gradle-setup@a4d662095a2f2af1ed24f1228eb6e55b0f9f1f29 with: java: 17 - distro: adopt + distro: temurin - name: Copy CI gradle.properties run: | mkdir -p ~/.gradle cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties - - name: Build and run tests - run: ./gradlew assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest + - name: Build the app + run: ./gradlew assembleStandardRelease + + - name: Run unit tests + run: ./gradlew testReleaseUnitTest testStandardReleaseUnitTest - name: Publish test report uses: mikepenz/action-junit-report@v5 diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index c81a5504cf..3c93697182 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -46,7 +46,7 @@ jobs: uses: null2264/actions/gradle-setup@a4d662095a2f2af1ed24f1228eb6e55b0f9f1f29 with: java: 17 - distro: adopt + distro: temurin - name: Setup CHANGELOG parser uses: taiki-e/install-action@parse-changelog @@ -80,6 +80,7 @@ jobs: run: | set -x echo "VERSION_TAG=v${{github.event.inputs.version}}" >> $GITHUB_ENV + echo "BUILD_TYPE=StandardRelease" >> $GITHUB_ENV # BETA - name: Prepare beta build @@ -88,9 +89,14 @@ jobs: set -x BETA_COUNT=$(git tag -l --sort=refname "v${{github.event.inputs.version}}-b*" | tail -n1 | sed "s/^\S*-b//g") - [ "$BETA_COUNT" = "" ] && BETA_COUNT="1" || BETA_COUNT=$((BETA_COUNT+1)) + if [ -z "$BETA_COUNT" ]; then + BETA_COUNT="1" + else + BETA_COUNT=$((BETA_COUNT+1)) + fi echo "VERSION_TAG=v${{github.event.inputs.version}}-b${BETA_COUNT}" >> $GITHUB_ENV + echo "BUILD_TYPE=StandardBeta" >> $GITHUB_ENV # NIGHTLY - name: Prepare nightly build @@ -98,23 +104,17 @@ jobs: run: | set -x echo "VERSION_TAG=r$(git rev-list --count HEAD)" >> $GITHUB_ENV + echo "BUILD_TYPE=StandardNightly" >> $GITHUB_ENV echo "COMMIT_HASH=$(git rev-parse HEAD)" >> $GITHUB_ENV echo "COMMIT_SHORT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - # PROD - - name: Build release build and run tests - if: startsWith(env.VERSION_TAG, 'v') && github.event.inputs.beta != 'true' - run: ./gradlew assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest + - name: Build the app + if: startsWith(env.BUILD_TYPE, 'Standard') + run: ./gradlew assemble${{ env.BUILD_TYPE }} - # BETA - - name: Build beta build and run tests - if: startsWith(env.VERSION_TAG, 'v') && github.event.inputs.beta == 'true' - run: ./gradlew assembleStandardBeta testReleaseUnitTest testStandardBetaUnitTest - - # NIGHTLY - - name: Build nightly build and run tests - if: startsWith(env.VERSION_TAG, 'r') - run: ./gradlew assembleStandardNightly testReleaseUnitTest testStandardNightlyUnitTest + - name: Run unit tests + if: startsWith(env.BUILD_TYPE, 'Standard') + run: ./gradlew testReleaseUnitTest test${{ env.BUILD_TYPE }}UnitTest - name: Upload R8 APK to artifact uses: actions/upload-artifact@v4 From 2208a8101307876376c1599c2b4a1d47206e4a36 Mon Sep 17 00:00:00 2001 From: MajorTanya <39014446+MajorTanya@users.noreply.github.com> Date: Wed, 5 Feb 2025 07:53:24 +0700 Subject: [PATCH 109/181] fix: Add Infinix system app to list of invalid browsers `com.transsion.resolver` being picked by the system as a suitable browser caused a Mihon user with an Infinix device to be unable to open any links in browsers, including tracker login and opening a WebView page in a real browser. --- CHANGELOG.md | 1 + .../kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd401f7b76..db65ba09d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Allow users to bypass onboarding's permission step if Shizuku is installed - Fix Recents page shows "No recent chapters" instead of a loading screen - Fix not fully loaded entries can't be selected on Library page +- Fix certain Infinix devices being unable to use any "Open link in browser" actions, including tracker setup (@MajorTanya) ### Other - Refactor Library to utilize Flow even more diff --git a/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt index 21ae2efd98..f02373cf79 100644 --- a/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt +++ b/core/main/src/androidMain/kotlin/eu/kanade/tachiyomi/util/system/DeviceUtil.kt @@ -67,9 +67,14 @@ object DeviceUtil { val invalidDefaultBrowsers = listOf( "android", + // Honor "com.hihonor.android.internal.app", + // Huawei "com.huawei.android.internal.app", + // Lenovo "com.zui.resolver", + // Infinix + "com.transsion.resolver", ) @SuppressLint("PrivateApi") From d6c5a9a7c26cf7cf8604a31740f273336392c7d3 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Mon, 10 Mar 2025 06:53:10 +0700 Subject: [PATCH 110/181] chore: Tweak .editorconfig [skip ci] --- .editorconfig | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/.editorconfig b/.editorconfig index 7bee4501e9..1b1fadfbcb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,13 +1,28 @@ +root = true + [*] charset = utf-8 -end_of_line = lf -indent_style=space -insert_final_newline=true +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true -[*.{json,json5}] -indent_size=2 +[*.xml] +indent_size = 4 +# noinspection EditorConfigKeyCorrectness [*.{kt,kts}] -indent_size=4 -ij_kotlin_allow_trailing_comma=true -ij_kotlin_allow_trailing_comma_on_call_site=true +indent_size = 4 +max_line_length = 120 + +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true +ij_kotlin_name_count_to_use_star_import = 2147483647 +ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 + +ktlint_code_style = intellij_idea +ktlint_function_naming_ignore_when_annotated_with = Composable +ktlint_standard_class-signature = disabled +ktlint_standard_discouraged-comment-location = disabled +ktlint_standard_function-expression-body = disabled +ktlint_standard_function-signature = disabled From d46f5fb73e6ca0e50eced475c73d7e0f5b48b91a Mon Sep 17 00:00:00 2001 From: Hiirbaf <42479509+Hiirbaf@users.noreply.github.com> Date: Fri, 28 Mar 2025 23:52:00 -0300 Subject: [PATCH 111/181] fix: Temporarily disable file log (#380) Temporary solution to the problem of stopping working in the background --- app/src/main/java/eu/kanade/tachiyomi/App.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 2fb9325bfd..8eb0338127 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -300,7 +300,7 @@ fun buildLogWritersToAdd( ) = buildList { if (!BuildConfig.DEBUG) add(CrashlyticsLogWriter()) - if (logPath != null && !BuildConfig.DEBUG) add(RollingUniFileLogWriter(logPath = logPath, isVerbose = isVerbose)) + // if (logPath != null && !BuildConfig.DEBUG) add(RollingUniFileLogWriter(logPath = logPath, isVerbose = isVerbose)) } private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE" From ebd891fa75b317277063640ce26a14a2dde237cb Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Sat, 12 Apr 2025 02:17:22 +0200 Subject: [PATCH 112/181] chore(i18n): Translations update from Hosted Weblate (#330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translate-URL: https://hosted.weblate.org/projects/yokai/yokai-plurals/fi/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/es/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/fi/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/fil/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/fr/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/id/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/ja/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/ru/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/tr/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/zh_Hans/ Translation: Yōkai/Yōkai Translation: Yōkai/Yōkai Plurals Co-authored-by: Ahmad Ansori Palembani Co-authored-by: Alexander Sergeev Co-authored-by: Hiirbaf Co-authored-by: Infy's Tagalog Translations Co-authored-by: MARTINAT Noah Co-authored-by: Ricky Tigg Co-authored-by: Tachibana Saza Co-authored-by: zhongfly <11155705+zhongfly@users.noreply.github.com> --- .../commonMain/moko-resources/es/strings.xml | 86 ++++++++++++++++- .../commonMain/moko-resources/fi/plurals.xml | 30 +++++- .../commonMain/moko-resources/fi/strings.xml | 93 +++++++++++++++++- .../commonMain/moko-resources/fil/strings.xml | 63 ++++++------ .../commonMain/moko-resources/fr/strings.xml | 21 ++++ .../commonMain/moko-resources/in/strings.xml | 23 +++++ .../commonMain/moko-resources/ja/strings.xml | 14 ++- .../commonMain/moko-resources/ru/strings.xml | 96 ++++++++++++++++++- .../commonMain/moko-resources/tr/strings.xml | 2 + .../moko-resources/zh-rCN/strings.xml | 4 +- 10 files changed, 388 insertions(+), 44 deletions(-) diff --git a/i18n/src/commonMain/moko-resources/es/strings.xml b/i18n/src/commonMain/moko-resources/es/strings.xml index f0289e1efa..32dafe0303 100644 --- a/i18n/src/commonMain/moko-resources/es/strings.xml +++ b/i18n/src/commonMain/moko-resources/es/strings.xml @@ -988,4 +988,88 @@ Información sobre la depuración Actividad en segundo plano Compartir la portada - \ No newline at end of file + Seleccione una carpeta donde %1$s almacenará descargas de capítulos, copias de seguridad y más.\n\nSe recomienda una carpeta dedicada.\n\nCarpeta seleccionada: %2$s + Para instalar la aplicación en actualizaciones. + Seleccionemos algunos valores predeterminados. Puedes cambiarlos más adelante en la configuración. + Actualizaciones + Evite interrupciones en las actualizaciones de bibliotecas, descargas y restauraciones de copias de seguridad de larga duración. + Toca dos veces para hacer zoom + Imprimir registros detallados en el registro del sistema (puede reducir el rendimiento de la aplicación) + Registro detallado + Repositorios de extensiones + Aún no has agregado ningún repositorio. + El repositorio %1$s tiene la misma huella de clave de firma que %2$s.\nSi esto es lo esperado, %2$s será reemplazado; de lo contrario, contacte con el responsable del repositorio. + Ubicación de almacenamiento + Recortar bordes (Long strip) + %s se encontró con un error inesperado. Le sugerimos que tome una captura de pantalla de este mensaje, descargue los registros de errores y luego lo comparta en el repositorio de GitHub. + Justo ahora + Usar la biblioteca de composición experimental + Aleatorio + Habilitar la acción de deslizar el capítulo + Extensiones maliciosas podrían leer las credenciales de inicio de sesión almacenadas o ejecutar código arbitrario.\n\nAl confiar en esta extensión, acepta estos riesgos. + Revocar todas las extensiones de confianza + ¿Revocar todas las extensiones de confianza? + No se pudo obtener acceso persistente a la carpeta. La aplicación podría tener un comportamiento inesperado. + Toque aquí para obtener ayuda con Cloudflare + Se requiere WebView para que la aplicación funcione + Atrás + Adelante + Refrescar + Error interno: %s + SFW + NSFW + Tipo de contenido + Se Produjo Un Error Inesperado + Reiniciar la aplicación + Refrescar + Selecciona una carpeta + Mostrar cola de descarga + Abrir último capítulo leído + Comportamiento al pulsar prolongadamente en Recientes + Abrir búsqueda global + Comportamiento al pulsar prolongadamente en Explorar + Abrir el menú de extensiones / migración + Mostrar contenido en el área recortada + Perfil de visualización personalizado + Datos y almacenamiento + Almacenamiento usado + Disponible: %1$s / Total: %2$s + Puede solucionar el problema con los conflictos entre los capítulos descargados cuando tienen el mismo nombre + Auto-anexar ID + ¿Actualizas desde una versión anterior y no sabes qué elegir? Consulta la sección de actualización de Tachiyomi en la guía de almacenamiento de Mihon para más información. + Guía de almacenamiento + Obligatorio + Opcional pero recomendado + Permiso para instalar aplicaciones + Permiso de notificación + Reciba notificaciones sobre actualizaciones de la biblioteca y más. + Uso de batería en segundo plano + Permitir + No hay ninguna ubicación de almacenamiento establecida + Ubicación no válida: %s + Ubicación no válida + Página %1$d de %2$d + No seleccionado + Seleccionado + Abrir una serie aleatoria (Global) + Default + El instalador Legacy aún no está implementado; actualmente se recurre al PackageInstaller (default) + Guardar páginas en carpetas separadas + Crea carpetas según el título del manga + Si el lector carga una imagen en blanco, reduzca gradualmente el umbral(Threshold).\nSeleccionado: %s + Incluir configuraciones sensibles (p. ej. tokens de inicio de sesión de trackers) + Última copia de seguridad automática: %s + Agregar nuevo repositorio + Repositorio de código abierto + Modo de depuración + Escanear almacenamientos externos en busca de entradas + Agregar repositorio + URL de repositorio no válida + ¡El repositorio ya existe! + ¿Eliminar repositorio? + ¿Está seguro de que desea eliminar el repositorio \"%s\"? + Reemplazar + La huella digital de la clave de firma ya existe + No se puede abrir la URL + Mover la serie al final + diff --git a/i18n/src/commonMain/moko-resources/fi/plurals.xml b/i18n/src/commonMain/moko-resources/fi/plurals.xml index 2e09ea4171..da473dce52 100644 --- a/i18n/src/commonMain/moko-resources/fi/plurals.xml +++ b/i18n/src/commonMain/moko-resources/fi/plurals.xml @@ -17,8 +17,8 @@ Nimikkeille %d - Valmistui %1$s virheitä löytyi %2$s - Valmistui %1$s virheitä löytyi %2$s + "Valmistui %1$s ja %2$s virhe" + "Valmistui %1$s ja %2$s virhettä" Välimuisti tyhjennetty. %d tiedosto on poistettu @@ -73,4 +73,28 @@ 1 puuttuva luku %d puuttuvaa lukua - \ No newline at end of file + + %d odottava päivitys + %d odottavaa päivitystä + + + %d laajennus päivitetty + %d laajennusta päivitetty + + + %d sarjatyyppi + %d sarjatyyppiä + + + %d lähde + %d lähdettä + + + %d tila + %d tilaa + + + %d kieli + %d kieltä + + diff --git a/i18n/src/commonMain/moko-resources/fi/strings.xml b/i18n/src/commonMain/moko-resources/fi/strings.xml index f7427294d6..381576cfff 100644 --- a/i18n/src/commonMain/moko-resources/fi/strings.xml +++ b/i18n/src/commonMain/moko-resources/fi/strings.xml @@ -529,7 +529,7 @@ Nollaa kaikki %1$s luvut Tämä poistaa \"%1$s\" -lukupäivän. Oletko varma\? Äskettäin lisätty - Viimeisimmät + Viimeaikaiset Päivitä kirjaston kannet myös kirjastoa päivitettäessä Päivitä kannet automaattisesti Sisällytä globaaliin päivitykseen @@ -721,7 +721,7 @@ Virheet Ei kirjanmerkeissä Lähteen järjestyksen mukaan - Laajat päivitykset voivat aiheuttaa suurempaa akun kulutusta, sekä lähteiden päivitysten hidastumista + Laajat päivitykset voivat johtaa akun käytön lisääntymiseen ja lähteiden hidastumiseen. Napauta saadaksesi lisätietoja. Varoitus: massalataukset voivat johtaa siihen, että lähteet muuttuvat hitaammiksi käyttää ja/tai ne estävät Tachiyomin käytön. Napauta saadaksesi lisätietoja. Vaikuttaa kirjaston kansiin ruudukossa Päivitystä ei voitu asentaa @@ -815,4 +815,91 @@ Poissuljetut kategoriat Asentaja Taustatoiminta - \ No newline at end of file + Varastoinnin opas + Näytä lisää lukuja + Ota luvun pyyhkäisytoiminto käyttöön + Päivitä sarja vain, jos siinä ei ole lukematonta lukua (lue kokonaan) + Poistetaanko edellinen jäljitin? + Satunnainen + Odottavat laajennuspäivitykset + MIUI-optimointi on poistettava käytöstä, jotta laajennukset voidaan asentaa. + Kumotetaanko kaikki luotetut laajennukset? + Jos lukija lataa tyhjän kuvan asteittain, pienennä kynnystä.\nValittu: %s + Näytä %1$s + Tämän merkinnän yksityiskohtiensivu on tarkasteltava ennen siirtoa + Lähteen asetukset + Kaikki luetut sarjat + Suodata scanlator-ryhmät + Palvelut, jotka tarjoavat lisäominaisuuksia tietyille lähteille. Merkintöjä jäljitetään automaattisesti, kun ne lisätään kirjastoosi. + Pitkä napautus Selauskäyttäytyminen + Käytä kokeellista luomiskirjastoa + Käytä porrastettua ruudukkoa + Haitalliset laajennukset voivat lukea mitä tahansa tallennettuja kirjautumistietoja tai suorittaa mielivaltaisen koodin.\n\nLuottamalla tähän laajennukseen hyväksyt nämä riskit. + Asennettu %1$s + Luo kansioita mangan otsikon mukaan + Herkkyys valikon piilottamiseen vierityksen yhteydessä + Sovelluksen asetukset + Päivitä jäljittäminen, kun se merkitään luetuksi + Päivitä jäljittäminen lukemisen jälkeen + Valitse merkintä + Lisää vaihtoehtoja + Navigoi ylös + Vaadittu + Valinnainen mutta suositeltavaa + Asenna sovellusten lupa + Sovelluksen asentaminen päivityksten yhteydessä. + Ilmoituslupa + Tallenna sivut erillisiin kansioihin + Jaa kansi + Tervetuloa! + Valitsemme joitain oletusasetuksia. Voit aina muuttaa näitä asioita myöhemmin asetuksista. + Aloita + Valitse kansio, johon %1$s tallentaa lukulataukset, varmuuskopiot ja paljon muuta.\n\nErillinen kansio on suositeltavaa.\n\nValittu kansio: %2$s + Valitse kansio + Päivitämässä vanhemmasta versiosta, eikä varma mitä valita? Katso lisätietoja Mihon-tallennusoppaan Tachiyomi-päivitysosiosta. + Saat ilmoituksen kirjaston päivityksistä ja muusta. + Tausta akun käyttö + Vältä keskeytyksiä pitkäaikaisissa kirjastopäivityksissä, latauksissa ja varmuuskopioiden palautuksissa. + Tallennussijaintia ei ole määritetty + Myönnä + Virheellinen sijainti: %s + Virheellinen sijainti + Kirjaston merkinnät + Sivu %1$d/%2$d + Uusi beta-versio saatavilla! + Ei valittu + Valittu + Avaa satunnainen sarja (laajamittainen) + Kielinimiöt + Ilmoitusten salliminen on suositeltavaa pitääksesi kirjastosi ja sovelluksesi ajan tasalla. + Lajittele noudetun ajan mukaan + Sarjojen mukaan + Viikon mukaan + Päivän mukaan + Näytä latausjono + Avaa viimeksi luettu luku + Kosketa pitkään Viimeaikaisten käyttäytyminen + Avaa laajennukset / siirtovalikko + Avaa laajamittaiinen haku + Oletus + Mahdollistaa laajennusten asentamisen ilman käyttäjän kehotteita ja mahdollistaa automaattiset päivitykset Android 12 -käyttöjärjestelmää käyttäville laitteille + Laajennusta ei voitu asentaa + Jotkut laajennukset saattavat silti pyytää asentamaan ne ensin. + Laajennuksia päivitetään + Laajennukset päivitetty + Kumoa kaikki luotetut laajennukset + Pitkä nauha + Ohita päällekkäiset luvut + Android 9.0:aa vanhemmissa laitteissa sinun on määritettävä katkaisuasetukset manuaalisesti järjestelmäasetuksista + Mukautettu näyttöprofiili + Mukautettu laitteiston bittikartan kynnys + Oletus (%d) + Zoomaa kaksoisnapauttamalla + Lisää tunniste + Odotuslista + Jäljitintä ei voi poistaa kohteesta %1$s ei-verkkotilassa + Laajamittaiset päivitykset + Päivitykset + Kirjasto päivitetty viimeksi: %s + Kutista ryhmitetyt luvut + diff --git a/i18n/src/commonMain/moko-resources/fil/strings.xml b/i18n/src/commonMain/moko-resources/fil/strings.xml index 7552093ea2..892225185f 100644 --- a/i18n/src/commonMain/moko-resources/fil/strings.xml +++ b/i18n/src/commonMain/moko-resources/fil/strings.xml @@ -137,7 +137,7 @@ I-update kung nakamit ang (mga) kondisyon Kategoryang kasama sa panlahatang update Bago - Nakaraan + Kasaysayan Walang nang resulta Walang nakitang resulta Lokal na Source @@ -405,7 +405,7 @@ In-update noong %1$s Tatanggalin nito ang petsa ng pagbasa sa \"%1$s\". Sigurado ka ba\? Mga bago - Bago & Nakaraan + Bago at kasaysayan Isama sa panlahatang update Petsa kinuha Mapusyaw @@ -421,13 +421,13 @@ Mga button sa baba ng pagbasa Ipakita bilang Pagpapakita - Shortcuts ng app + Mga shortcut ng app Itago kapag nag-scroll Itago agad ang nabigasyon sa baba Simula Pindutin ang Balik para pumunta sa simula - Ipagpaubaya - Ipagpaubaya + Gamitin ang default + Default ng sistema Itakda bilang default para sa lahat Parametro sa paghahanap (hal. language:filipino) I-filter ang mga wika @@ -497,7 +497,7 @@ Huling ginamit (Aklatan o Kamakailan) Purong itim Bumalik sa simula - Dami ng kusang pag-backup + Dami ng awtomatikong backup Bigong ma-restore ang backup Maaaring hindi gumana nang maayos ang pag-backup/pag-restore kung nakasara ang MIUI optimization. Bina-backup na @@ -545,23 +545,23 @@ Nito lamangg Ipakita muna ang pamagat Ipakita ang mga nabasang kabanata sa Nakagrupo at Lahat - Ipakita ang I-reset ang nakaraan + Ipakita ang I-reset ang kasaysayan Na-download lang Di pa nabasa Di pa nabasa o na-download Ipakita ang I-download - Mari-reset din ng pagpindot nang matagal ang nakaraan sa kabanata + Pindutin nang matagal ay maaari ding i-reset ang kasaysayan ng kabanata I-reset ang kabanata\? Nakagrupo Walang kamakailang nabasa Walang kamakailang kabanata Hanapin sa kamakailan… - Tingnan ang nakaraan + Tingnan ang kasaysayan Dinagdag %1$s Huling binasa %1$s Nabasa %1$s I-reset ang lahat ng mga kabanata para sa %1$s - I-reset ang nakaraan + I-reset ang kasaysayan Kadadagdag Kamakailan Paminsan-minsang magpapakita ng mga mungkahi sa paghahanap. I-long tap ang mungkahi para hanapin ito. @@ -569,7 +569,7 @@ Makikita ang ilang mga button sa ibang lugar kung nakapatay dito Panlahatang pag-update Rine-refresh ang mga cover sa aklatan at habang nag-a-update - Kusang i-refresh ang mga cover + Awtomatikong i-refresh ang mga cover Kapag paalpabetong mag-aayos, ayusin nang binabalewala ang mga article (a, an, the) sa simula ng mga pamagat Ayusin nang binabalewala ang mga article Lumipat nang dalawahang pahina @@ -620,12 +620,12 @@ Baba Mag-download na Babala - Hinihinto ang pagtala sa mga binabasa + Nahihinto ang pagbabasa ng kasaysayan Yotsuba Kasama: %s Presas Mga Gawain - Ipagpaubaya + I-set bilang default Di tumatakbo ang Shizuku Paki-install at buksan ang Shizuku para magamit ito bilang taga-install ng extension. Walang tumugmang nakita @@ -696,7 +696,7 @@ Tanggalin ang pag-track sa app Lumipat na Malinaw na Ikaw (Dynamic) - Buksan agad kung may bagong kabanata + Ang mga shortcut ng serye ay nagbubukas ng mga bagong kabanata Gagana lang sa Patayo ang gagawin sa cutout area para sa mga piling paraan ng pagsasalaki Ipakita ang mga kabanatang na-download Idagdag ang %1$s sa Aklatan\? @@ -724,8 +724,8 @@ Di matatanggal ang tracker mula sa %1$s habang offline Buksan ang %s Maliwanag na tema - Ipagpaubaya - Kung ipagpapaubaya, gagamitin ang nabigasyon sa gilid para sa ilang mga phone at maliliit na tablet habang nakahiga, at palaging magpapakita ito sa mga malalaking tablet + Default na pagkilos + Bilang default, ginagamit ang side navigation para sa ilang mga telepono at maliliit na tablet kapag nasa landscape, at laging ipinapakita sa mas malalaking tablet May bagong beta na bersyon! Tanggalin ang nakaraang tracker\? Yin @@ -736,9 +736,9 @@ Yang Pahina ng detalye Pinalawak na toolbar - Kusang i-update ang app + Awtomatikong i-update ang app Sa kahit anong network - Wag magkusang mag-update + Huwag awtomatikong mag-update Gamitin ang di-pantay na grid I-update lang ang manga kung walang kabanatang di pa nabasa (lahat nabasa) Pareho @@ -752,7 +752,7 @@ Gamitin ang source na may pinakamaraming kabanata (mas mabagal) Isara ang %s Madilim na tema - Kusang mag-update + Awtomatikong mag-update Ipakita ang mga huling ginamit na source Nalinis na ang WebView data Linisin ang WebView data @@ -769,7 +769,7 @@ Hatiin ang dobleng pahina Kung nakapatay, lalaktawan ang pahina ng paglipat kung naka-load na ang susunod na kabanata Simulan ang nakalipas na cutout - Awto (base sa orientation) + Awtomatiko (base sa orientation) Lahat ng kabanata Lahat ng di pa nabasa Parehong panatilihin sa %1$s at palitan lang nang lokal @@ -788,23 +788,23 @@ Takipsilim Lime Time Maaaring humantong sa mga isyu ang pagbalik sa mas lumang bersyon at baka kailanganin nito ang paglilinis sa data ng app. - Burahin ang nakaraan ng mga entry na hindi naka-save sa aklatan mo + Burahin ang kasaysayan ng mga entry na hindi naka-save sa iyong aklatan I-refresh ang metadata ng pagta-track Maaaring di stable ang mga beta at baka kailanganin nito ang paglilinis sa data ng app. Linisin lahat ng mga na-download na kabanata Subukan ang mga bagong tampok bago sila opisyal na mailabas. Maaaring di stable ang mga beta at ginawa upang magbigay ng feedback sa developer. I-refresh ang metadata ng Aklatan - Linisin ang nakaraan - Nalinis na ang nakaraan - Sigurado ka ba talaga\? Mawawala ang buong nakaraan. + Linisin ang kasaysayan + Nalinis na ang kasaysayan + Sigurado ka ba talaga? Mawawala ang buong kasaysayan. I-save bilang CBZ archive Error: walang URI - Rinerekomenda po namin ang kusang pag-backup. Kailangan mo ring magtabi ng mga kopya sa ibang lugar. + Lubos na inirerekomenda ang awtomatikong backup. Dapat ka ring magtago ng mga kopya sa iba pang mga lugar. Pamahalaan ang mga abiso Di pa lowbat Mga Madalas Itanong at Gabay Linisin ang mga filter - Panimulang orientation + Default na orientation Orientation Nai-login na Bawal bakante ang username o password @@ -859,8 +859,8 @@ Walang lilinising folder Patayin ito kung nakakaranas ka ng mga isyu sa pag-update o pag-restore sa Aklatan mo Linisin ang mga naka-cache na cover - Kusang i-update ang mga mga extension - Maaaring hindi kusang mag-update ang ilang mga mga extension kung in-install ito sa labas ng app na ito + Awtomatikong i-update ang mga extension + Maaaring hindi awtomatikong mag-update ang ilang mga extension kung in-install ito sa labas ng app na ito Anong bago sa release na ito Nakatutulong na link sa pagsalin Palaging panatilihin @@ -902,7 +902,7 @@ Maaaring mangailangan ang ilang wika ng muling pag-restart ng app upang maipakita ito Patuloy na magbasa ng mga kabanata Mga nabasang manga - Kusang mag-download habang nagbabasa + Awtomatikong mag-download habang nagbabasa Bilang ng na-download Istatistika Stat @@ -926,7 +926,7 @@ Tracker Haba Sinimulang taon - Oras na ginugol sa pambasa, base sa nakaraang binasa + Oras na ginugol sa reader, base sa kasaysayan ng kabanata Nilaktawan dahil hindi kailangan ang pag-update ng serye Inbalidong string ng user agent Burahin din ang may pananda @@ -1050,4 +1050,5 @@ I-save ang mga pahina sa magkakaibang folder Custom na profile sa display Maaaring maayos ang isyu sa mga na-download na kabanata na magkasalungat sa isa\'t isa kapag pareho sila ng pangalan - \ No newline at end of file + Gumamit ng eksperimental na compose library + diff --git a/i18n/src/commonMain/moko-resources/fr/strings.xml b/i18n/src/commonMain/moko-resources/fr/strings.xml index 63ae623199..49f8c33278 100644 --- a/i18n/src/commonMain/moko-resources/fr/strings.xml +++ b/i18n/src/commonMain/moko-resources/fr/strings.xml @@ -986,4 +986,25 @@ Entrées de la bibliothèque Informations de débogage Activité en arrière-plan + Autorisation de notification + Plus d\'Options + Naviguer vers le haut + Bienvenue! + Choisissons quelques valeurs par défaut. Vous pourrez toujours les modifier ultérieurement dans les paramètres. + Commencer + Sélectionnez un dossier dans lequel %1$s stockera les téléchargements de chapitres, les sauvegardes et plus encore.\n\nUn dossier dédié est recommandé.\n\nDossier sélectionné : %2$s + Sélectionner un dossier + Guide de stockage + Requis + Facultatif mais recommandé + Autorisation d\'installation d\'applications + Évitez les interruptions des mises à jour de bibliothèque, des téléchargements et des restaurations de sauvegarde de longue durée. + Accorder + Soyez informé des mises à jour de la bibliothèque et plus encore. + Utilisation de la batterie en arrière-plan + Aucun emplacement de stockage défini + Emplacement non valide: %s + Emplacement invalide + Vous effectuez une mise à jour à partir d\'une ancienne version et vous ne savez pas quoi sélectionner ? Reportez-vous à la section Mise à niveau Tachiyomi dans le guide de stockage Mihon pour plus d\'information. + Pour installer l\'application sur les mises à jour. \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/in/strings.xml b/i18n/src/commonMain/moko-resources/in/strings.xml index 3e59525baa..1fa98db27f 100644 --- a/i18n/src/commonMain/moko-resources/in/strings.xml +++ b/i18n/src/commonMain/moko-resources/in/strings.xml @@ -956,4 +956,27 @@ Selamat datang! Opsi lebih lanjut Segarkan + Sekarang + Tambah repo + Tambah repo baru + Pengaturan aplikasi + Doki + Penggunaan penyimpanan + Lokasi penyimpanan + Data dan penyimpanan + Tersedia: %1$s / Total: %2$s + Hapus repo? + Maju + Kembali + Segarkan + Tipe konten + Tidak dapat membuka url + Bawaan (%d) + Ganti + Pengaturan sumber + Bawaan + Mode debug + Url repo tidak valid + Masalah internal: %s + Acak \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ja/strings.xml b/i18n/src/commonMain/moko-resources/ja/strings.xml index 2e83cb41dd..77adcbfdc4 100644 --- a/i18n/src/commonMain/moko-resources/ja/strings.xml +++ b/i18n/src/commonMain/moko-resources/ja/strings.xml @@ -8,7 +8,7 @@ 旧バージョンからアップデートし、何を選ぶべきか分からない場合は、MihonストレージガイドのTachiyomi upgradeセクションを参照してください。 ストレージガイド 必須 - オプションですが、お勧めします。 + オプションですが、お勧めします アプリのインストール権限 ソース拡張機能をインストールするために必要です。 通知の権限 @@ -334,7 +334,7 @@ 読み終わりました: 読んでいます: 次: - 前: + 先: 次の章がありません 前の章がありません ページをロード中… @@ -447,7 +447,7 @@ カットアウト領域外から描画 カットアウト領域の動作は、特定のスケールタイプの縦向きモードでのみ適用されます レガシーカットアウト設定を開く - Android 9.0 より古いデバイスでは、システム設定から手動で設定する以外に、カットアウト設定を変更する方法はありません。 + Android 9.0 より古いデバイスでは、システム設定から手動で設定する以外に、カットアウト設定を変更する方法はありません カットアウト領域にコンテンツを表示 カットアウト領域を無視 ページレイアウト @@ -1069,4 +1069,10 @@ %s が予期しないエラーに遭遇しました。このメッセージをスクリーンショットにしてクラッシュログを保存し、GitHub Issue に共有することをお勧めします。 アプリを再起動 更新 - \ No newline at end of file + 無作為 + もっと設定 + ページ%1$dから%2$d + 不選択 + 選択 + 更新 + diff --git a/i18n/src/commonMain/moko-resources/ru/strings.xml b/i18n/src/commonMain/moko-resources/ru/strings.xml index 31efbdc048..4c89bae656 100644 --- a/i18n/src/commonMain/moko-resources/ru/strings.xml +++ b/i18n/src/commonMain/moko-resources/ru/strings.xml @@ -985,4 +985,98 @@ Настройки приложения Отладочная информация Фоновая активность - \ No newline at end of file + Открыть меню расширений/миграции + Открыть глобальный поиск + Реакция на долгий тап в поиске + Старый метод установки ещё не реализован, используем стандартный PackageInstaller + Вредоносные расширения могут получить доступ к чтению и изменению данных входа или выполнять произвольный код.\n\nДоверяя этому расширению, вы действуете на свой страх и риск. + Отозвать доверие у всех расширений + Отозвать доверие у всех расширений? + Длинные страницы + Использовать экспериментальную отрисовку + Случайно + Добавить репозиторий + Давайте установим начальные параметры. Вы всегда можете позже изменить их в настройках. + Стр. %1$d из %2$d + Обновились со старой версии и не уверены, что выбрать? Для более подробного объяснения прочтите пункт \"обновление с Tachiyomi\" в инструкции по настройке хранилища Mihon. + Переустановка при обновлении. + Обрезать поля (Длинные страницы) + Открыть устаревшие настройки области выреза + На устройствах с версией Android старше 9.0, необходимо настраивать область выреза вручную в системных настройках + Выбрать папку + Предоставить + Больше опций + Вверх + Начать + Добро пожаловать! + Выберите папку, где %1$s будет сохранять загруженные главы, бэкапы и прочее.\n\nРекомендуется выбрать отдельную папку.\n\nВыбрана папка: %2$s + Инструкция по настройке хранилища + Обязательно + Не выбрано + Выбрано + Обновления + Разрешить фоновую установку расширений и автоматическое обновление приложения без запроса для устройств с версией Android 11 и старше + Необязательно, но рекомендуется + Предоставьте права приложению + Показывать уведомления + Уведомления об обновлении библиотеки и прочие. + Использование батареи при работе в фоне + Не прерывайте долгие обновления библиотеки, загрузку и восстановление из бэкапа. + Не выбрана папка для хранения + Неверный путь: %s + Неверный путь + Открыть случайную серию (Глобально) + Включить действие при свайпе главы + Открыть последнюю прочитанную главу + Реакция на долгий тап в недавних + Показать очередь загрузки + Показывать содержимое в области выреза + Сохранять страницы в отдельных папках + Создаёт папки по названию манги + Свой профиль отображения + Искать записи на внешних носителях + Исправляет ошибку конфликта загружаемых глав, когда они имеют одно название + Писать логи в системный лог (может снизить производительность) + Включить персональные данные (в т.ч. токены входа в аккаунты трекеров) + Неверная ссылка репозитория + У репозиториев %1$s и %2$s одинаковый отпечаток ключа подписи.\nЕсли так и должно быть, то репозиторий %2$s будет заменён, иначе свяжитесь с держателем репозитория. + Не удалось получить постоянный доступ к папке. Поведение приложения может быть непредсказуемым. + Только что + %s завершилась из-за ошибки. Пожалуйста, сделайте скриншот этого сообщения, сохраните логи вылета, и откройте с ними новую Issue на GitHub. + Свой порог чувствительности маски + Увеличивает чувствительность, если отрисована пустая страница.\nВыбрано: %s + Присоединять ID к имени + Поделиться обложкой + Песок + Вебкомикс + Внутренняя ошибка: %s + Тип содержимого + Папка хранилища + Занятое место + Данные и хранилище + Свободно: %1$s / Всего: %2$s + Добавить новый репозиторий + Репозитории расширений + Заменить + Отпечаток ключа подписи уже существует + Тапните здесь для помощи с Cloudflare + Перезапустить приложение + Обновить + Возникла неожиданная ошибка + Показывать логи + Невозможно перейти по ссылке + Открыть репозиторий + Двойной тап для приближения + Последний автоматический бэкап: %s + Режим отладки + Репозиторий уже добавлен! + Вы ещё не добавили ни одного репозитория. + Вы уверены, что хотите удалить репозиторий \"%s\"? + Переместить серию в конец + Для этого приложения требуется WebView + Назад + Вперёд + По умолчанию (%d) + Удалить репозиторий? + Обновить + diff --git a/i18n/src/commonMain/moko-resources/tr/strings.xml b/i18n/src/commonMain/moko-resources/tr/strings.xml index 169ea722a5..9bc05e96bc 100644 --- a/i18n/src/commonMain/moko-resources/tr/strings.xml +++ b/i18n/src/commonMain/moko-resources/tr/strings.xml @@ -956,4 +956,6 @@ Arka plan etkinliği Kaynak ayarları Uygulama ayarları + Daha fazla + Hoşgeldiniz! \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml index 639638f565..96d4879a3c 100644 --- a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml @@ -1017,7 +1017,7 @@ %1$s 仓库的签名密钥指纹与 %2$s 仓库相同。\n如果继续,%2$s 将被替换,否则请联系仓库的维护者。 打开图源仓库 无法打开网址 - 自动附件ID + 自动附加ID 刷新 SFW NSFW @@ -1049,4 +1049,6 @@ 刚才 内部错误:%s 重启应用 + 乱序 + 使用实验性的书架UI(基于Compose框架) \ No newline at end of file From 4faa641739c930293335403a2a698e284795b098 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sat, 12 Apr 2025 07:18:19 +0700 Subject: [PATCH 113/181] docs: Sync changelog [skip ci] --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db65ba09d2..b1d3cc0371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,18 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co ### Additions - Add random library sort +### Changes +- Temporarily disable log file + ### Fixes - Allow users to bypass onboarding's permission step if Shizuku is installed - Fix Recents page shows "No recent chapters" instead of a loading screen - Fix not fully loaded entries can't be selected on Library page - Fix certain Infinix devices being unable to use any "Open link in browser" actions, including tracker setup (@MajorTanya) +### Translation +- Update translations from Weblate + ### Other - Refactor Library to utilize Flow even more - Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.1 From 7a08ca294afa140196b4d6f3f588726a4a7316f2 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 13 Apr 2025 18:21:30 +0700 Subject: [PATCH 114/181] fix: Fix source filter buttom sheet unable to be fully scrolled to the bottom This bug has been annoying me for a long time, classic RecyclerView moment --- CHANGELOG.md | 1 + .../ui/source/browse/SourceFilterSheet.kt | 21 +++++++++------- .../recyclerview/VertPaddingDecoration.kt | 25 +++++++++++++++++++ .../main/res/layout/source_filter_sheet.xml | 18 ++++++------- 4 files changed, 45 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/yokai/presentation/component/recyclerview/VertPaddingDecoration.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index b1d3cc0371..cd31bdfea6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Fix Recents page shows "No recent chapters" instead of a loading screen - Fix not fully loaded entries can't be selected on Library page - Fix certain Infinix devices being unable to use any "Open link in browser" actions, including tracker setup (@MajorTanya) +- Fix source filter bottom sheet unable to be fully scrolled to the bottom ### Translation - Update translations from Weblate diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt index 14be0b9ae3..9200f41ca9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewTreeObserver.OnGlobalLayoutListener import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.withStyledAttributes import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.updateLayoutParams @@ -21,6 +22,7 @@ import eu.kanade.tachiyomi.util.view.checkHeightThen import eu.kanade.tachiyomi.util.view.collapse import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsetsCompat import eu.kanade.tachiyomi.widget.E2EBottomSheetDialog +import yokai.presentation.component.recyclerview.VertPaddingDecoration import android.R as AR class SourceFilterSheet(val activity: Activity) : @@ -92,6 +94,7 @@ class SourceFilterSheet(val activity: Activity) : } binding.filtersRecycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) + binding.filtersRecycler.addItemDecoration(VertPaddingDecoration(12.dpToPx)) binding.filtersRecycler.clipToPadding = false binding.filtersRecycler.adapter = adapter binding.filtersRecycler.setHasFixedSize(false) @@ -132,17 +135,17 @@ class SourceFilterSheet(val activity: Activity) : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val attrsArray = intArrayOf(AR.attr.actionBarSize) - val array = context.obtainStyledAttributes(attrsArray) - val headerHeight = array.getDimensionPixelSize(0, 0) - binding.titleLayout.updatePaddingRelative( - bottom = activity.window.decorView.rootWindowInsetsCompat - ?.getInsets(systemBars())?.bottom ?: 0, - ) + context.withStyledAttributes(null, attrsArray) { + val headerHeight = getDimensionPixelSize(0, 0) + binding.titleLayout.updatePaddingRelative( + bottom = activity.window.decorView.rootWindowInsetsCompat + ?.getInsets(systemBars())?.bottom ?: 0, + ) - binding.titleLayout.updateLayoutParams { - height = headerHeight + binding.titleLayout.paddingBottom + binding.titleLayout.updateLayoutParams { + height = headerHeight + binding.titleLayout.paddingBottom + } } - array.recycle() } private fun updateBottomButtons() { diff --git a/app/src/main/java/yokai/presentation/component/recyclerview/VertPaddingDecoration.kt b/app/src/main/java/yokai/presentation/component/recyclerview/VertPaddingDecoration.kt new file mode 100644 index 0000000000..6aba55167d --- /dev/null +++ b/app/src/main/java/yokai/presentation/component/recyclerview/VertPaddingDecoration.kt @@ -0,0 +1,25 @@ +package yokai.presentation.component.recyclerview + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +/** + * Add (vertical) padding to RecyclerView first and last item. Because `clipToPadding = "false"` bugged out for Bottom Sheets. + */ +class VertPaddingDecoration(private val padding: Int) : RecyclerView.ItemDecoration() { + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + super.getItemOffsets(outRect, view, parent, state) + + val itemPosition = parent.getChildAdapterPosition(view) + + if (itemPosition == RecyclerView.NO_POSITION) return; + + when { + itemPosition == 0 -> + outRect.top = padding + itemPosition > 0 && itemPosition == state.itemCount - 1 -> + outRect.bottom = padding + } + } +} diff --git a/app/src/main/res/layout/source_filter_sheet.xml b/app/src/main/res/layout/source_filter_sheet.xml index 99179647c6..54bc53f241 100644 --- a/app/src/main/res/layout/source_filter_sheet.xml +++ b/app/src/main/res/layout/source_filter_sheet.xml @@ -1,13 +1,12 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/source_filter_sheet" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/bottom_sheet_rounded_background" + app:layout_constraintVertical_chainStyle="packed" + android:backgroundTint="?attr/background"> From f13f98f19a568945e1f26186d40634d8d2229de0 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 14 Apr 2025 21:02:12 +0700 Subject: [PATCH 115/181] feat: Add the ability to save search queries I got tired of putting the same tag over and over, so... --- CHANGELOG.md | 1 + app/build.gradle.kts | 1 + .../source/browse/BrowseSourceController.kt | 156 +++++++++++----- .../ui/source/browse/BrowseSourcePresenter.kt | 53 ++++++ .../ui/source/browse/SavedSearchExtensions.kt | 45 +++++ .../ui/source/browse/SavedSearchesAdapter.kt | 93 ++++++++++ .../ui/source/browse/SourceFilterSheet.kt | 57 +++--- .../system/MaterialAlertDialogExtensions.kt | 26 ++- .../main/java/yokai/core/di/DomainModule.kt | 12 ++ .../source/browse/filter/FilterSerializer.kt | 94 ++++++++++ .../browse/filter/FilterTypeSerializer.kt | 167 ++++++++++++++++++ .../filter/interactor/DeleteSavedSearch.kt | 9 + .../filter/interactor/GetSavedSearch.kt | 13 ++ .../filter/interactor/InsertSavedSearch.kt | 9 + .../browse/filter/models/SavedSearch.kt | 10 ++ .../res/layout/browse_source_controller.xml | 3 +- app/src/main/res/layout/dialog_text_input.xml | 19 ++ .../main/res/layout/source_filter_sheet.xml | 39 ++-- .../source_filter_sheet_saved_search.xml | 27 +++ data/build.gradle.kts | 1 + .../filter/SavedSearchRepositoryImpl.kt | 37 ++++ .../sqldelight/tachiyomi/data/saved_search.sq | 35 ++++ .../sqldelight/tachiyomi/migrations/28.sqm | 8 + .../browse/filter/SavedSearchRepository.kt | 14 ++ .../browse/filter/models/RawSavedSearch.kt | 25 +++ .../moko-resources/base/strings.xml | 6 + 26 files changed, 880 insertions(+), 80 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchExtensions.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchesAdapter.kt create mode 100644 app/src/main/java/yokai/domain/source/browse/filter/FilterSerializer.kt create mode 100644 app/src/main/java/yokai/domain/source/browse/filter/FilterTypeSerializer.kt create mode 100644 app/src/main/java/yokai/domain/source/browse/filter/interactor/DeleteSavedSearch.kt create mode 100644 app/src/main/java/yokai/domain/source/browse/filter/interactor/GetSavedSearch.kt create mode 100644 app/src/main/java/yokai/domain/source/browse/filter/interactor/InsertSavedSearch.kt create mode 100644 app/src/main/java/yokai/domain/source/browse/filter/models/SavedSearch.kt create mode 100644 app/src/main/res/layout/dialog_text_input.xml create mode 100644 app/src/main/res/layout/source_filter_sheet_saved_search.xml create mode 100644 data/src/commonMain/kotlin/yokai/data/source/browse/filter/SavedSearchRepositoryImpl.kt create mode 100644 data/src/commonMain/sqldelight/tachiyomi/data/saved_search.sq create mode 100644 data/src/commonMain/sqldelight/tachiyomi/migrations/28.sqm create mode 100644 domain/src/commonMain/kotlin/yokai/domain/source/browse/filter/SavedSearchRepository.kt create mode 100644 domain/src/commonMain/kotlin/yokai/domain/source/browse/filter/models/RawSavedSearch.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index cd31bdfea6..be74d403d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co ### Additions - Add random library sort +- Add the ability to save search queries ### Changes - Temporarily disable log file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 75a95ee35e..deb59dc23a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -25,6 +25,7 @@ fun runCommand(command: String): String { return result.standardOutput.asText.get().trim() } +@Suppress("PropertyName") val _versionName = "1.9.8" val betaCount by lazy { val betaTags = runCommand("git tag -l --sort=refname v${_versionName}-b*") diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt index 5890885938..76aa137449 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceController.kt @@ -10,6 +10,9 @@ import android.view.View import android.view.ViewGroup import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ExploreOff +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.core.view.WindowInsetsCompat.Type.ime import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.isVisible @@ -46,7 +49,9 @@ import eu.kanade.tachiyomi.util.system.connectivityManager import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.e import eu.kanade.tachiyomi.util.system.launchIO +import eu.kanade.tachiyomi.util.system.materialAlertDialog import eu.kanade.tachiyomi.util.system.openInBrowser +import eu.kanade.tachiyomi.util.system.setTextInput import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.withUIContext import eu.kanade.tachiyomi.util.view.activityBinding @@ -56,7 +61,11 @@ import eu.kanade.tachiyomi.util.view.inflate import eu.kanade.tachiyomi.util.view.isControllerVisible import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.setAction +import eu.kanade.tachiyomi.util.view.setMessage +import eu.kanade.tachiyomi.util.view.setNegativeButton import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener +import eu.kanade.tachiyomi.util.view.setPositiveButton +import eu.kanade.tachiyomi.util.view.setTitle import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.widget.AutofitRecyclerView @@ -68,6 +77,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import uy.kohesive.injekt.injectLazy import yokai.domain.manga.interactor.GetManga +import yokai.domain.source.browse.filter.models.SavedSearch import yokai.i18n.MR import yokai.presentation.core.icons.CustomIcons import yokai.presentation.core.icons.LocalSource @@ -140,6 +150,9 @@ open class BrowseSourceController(bundle: Bundle) : private var filterSheet: SourceFilterSheet? = null private var lastPosition: Int = -1 + // Basically a cache just so the filter sheet is shown faster + var savedSearches by mutableStateOf(emptyList()) + private val isBehindGlobalSearch: Boolean get() = router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller is GlobalSearchController @@ -183,6 +196,7 @@ open class BrowseSourceController(bundle: Bundle) : binding.fab.isVisible = presenter.sourceFilters.isNotEmpty() binding.fab.setOnClickListener { showFilters() } + activityBinding?.appBar?.y = 0f activityBinding?.appBar?.updateAppBarAfterY(recycler) activityBinding?.appBar?.lockYPos = true @@ -376,12 +390,16 @@ open class BrowseSourceController(bundle: Bundle) : return true } + private fun applyFilters() { + val allDefault = presenter.filtersMatchDefault() + showProgressBar() + adapter?.clear() + presenter.setSourceFilter(if (allDefault) FilterList() else presenter.sourceFilters) + updatePopLatestIcons() + } + private fun showFilters() { if (filterSheet != null) return - val sheet = SourceFilterSheet(activity!!) - filterSheet = sheet - sheet.setFilters(presenter.filterItems) - presenter.filtersChanged = false val oldFilters = mutableListOf() for (i in presenter.sourceFilters) { if (i is Filter.Group<*>) { @@ -394,50 +412,94 @@ open class BrowseSourceController(bundle: Bundle) : oldFilters.add(i.state) } } - sheet.onSearchClicked = { - var matches = true - for (i in presenter.sourceFilters.indices) { - val filter = oldFilters.getOrNull(i) - if (filter is List<*>) { - for (j in filter.indices) { - if (filter[j] != - ( - (presenter.sourceFilters[i] as Filter.Group<*>).state[j] as - Filter<*> - ).state - ) { - matches = false - break - } - } - } else if (filter != presenter.sourceFilters[i].state) { - matches = false - break - } - if (!matches) break - } - if (!matches) { - val allDefault = presenter.filtersMatchDefault() - showProgressBar() - adapter?.clear() - presenter.setSourceFilter(if (allDefault) FilterList() else presenter.sourceFilters) - updatePopLatestIcons() - } - } - sheet.onResetClicked = { - presenter.appliedFilters = FilterList() - val newFilters = presenter.source.getFilterList() - presenter.sourceFilters = newFilters - sheet.setFilters(presenter.filterItems) - } - sheet.setOnDismissListener { - filterSheet = null - } - sheet.setOnCancelListener { - filterSheet = null - } - sheet.show() + filterSheet = SourceFilterSheet( + activity = activity!!, + searches = { savedSearches }, + onSearchClicked = { + var matches = true + for (i in presenter.sourceFilters.indices) { + val filter = oldFilters.getOrNull(i) + if (filter is List<*>) { + for (j in filter.indices) { + if (filter[j] != + ( + (presenter.sourceFilters[i] as Filter.Group<*>).state[j] as + Filter<*> + ).state + ) { + matches = false + break + } + } + } else if (filter != presenter.sourceFilters[i].state) { + matches = false + break + } + if (!matches) break + } + if (!matches) { + applyFilters() + } + }, + onResetClicked = { + presenter.appliedFilters = FilterList() + val newFilters = presenter.source.getFilterList() + presenter.sourceFilters = newFilters + filterSheet?.setFilters(presenter.filterItems) + }, + onSaveClicked = { + viewScope.launchIO { + val names = presenter.loadSearches().map { it.name } + var searchName = "" + withUIContext { + activity!!.materialAlertDialog() + .setTitle(activity!!.getString(MR.strings.save_search)) + .setTextInput(hint = activity!!.getString(MR.strings.save_search_hint)) { input -> + searchName = input + } + .setPositiveButton(MR.strings.save) { _, _ -> + if (searchName.isNotBlank() && searchName !in names) { + presenter.saveSearch(searchName.trim(), presenter.query, presenter.sourceFilters) + filterSheet?.scrollToTop() + } else { + activity!!.toast(MR.strings.save_search_invalid_name) + } + } + .setNegativeButton(MR.strings.cancel, null) + .show() + } + } + }, + onSavedSearchClicked = ss@{ searchId -> + viewScope.launchIO { + val search = presenter.loadSearch(searchId) // Grab the latest data from database + if (search?.filters == null) return@launchIO + + withUIContext { + presenter.sourceFilters = search.filters + filterSheet?.setFilters(presenter.filterItems) + // This will call onSaveClicked() + filterSheet?.dismiss() + } + } + }, + onDeleteSavedSearchClicked = { searchId -> + activity!!.materialAlertDialog() + .setTitle(MR.strings.save_search_delete) + .setMessage(MR.strings.save_search_delete) + .setPositiveButton(MR.strings.cancel, null) + .setNegativeButton(android.R.string.ok) { _, _ -> presenter.deleteSearch(searchId) } + .show() + } + ) + filterSheet?.setFilters(presenter.filterItems) + presenter.filtersChanged = false + + filterSheet?.setOnCancelListener { filterSheet = null } + filterSheet?.setOnDismissListener { filterSheet = null } + + filterSheet?.show() } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt index 693e04c267..8ed61311f0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourcePresenter.kt @@ -28,6 +28,7 @@ import eu.kanade.tachiyomi.ui.source.filter.TextSectionItem import eu.kanade.tachiyomi.ui.source.filter.TriStateItem import eu.kanade.tachiyomi.ui.source.filter.TriStateSectionItem import eu.kanade.tachiyomi.util.system.launchIO +import eu.kanade.tachiyomi.util.system.launchNonCancellableIO import eu.kanade.tachiyomi.util.system.withUIContext import kotlinx.coroutines.Job import kotlinx.coroutines.flow.asFlow @@ -35,8 +36,12 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy @@ -44,6 +49,11 @@ import yokai.domain.manga.interactor.GetManga import yokai.domain.manga.interactor.InsertManga import yokai.domain.manga.interactor.UpdateManga import yokai.domain.manga.models.MangaUpdate +import yokai.domain.source.browse.filter.FilterSerializer +import yokai.domain.source.browse.filter.interactor.DeleteSavedSearch +import yokai.domain.source.browse.filter.interactor.GetSavedSearch +import yokai.domain.source.browse.filter.interactor.InsertSavedSearch +import yokai.domain.source.browse.filter.models.SavedSearch import yokai.domain.ui.UiPreferences // FIXME: Migrate to Compose @@ -63,6 +73,11 @@ open class BrowseSourcePresenter( private val insertManga: InsertManga by injectLazy() private val updateManga: UpdateManga by injectLazy() + private val deleteSavedSearch: DeleteSavedSearch by injectLazy() + private val getSavedSearch: GetSavedSearch by injectLazy() + private val insertSavedSearch: InsertSavedSearch by injectLazy() + private val filterSerializer: FilterSerializer by injectLazy() + /** * Selected source. */ @@ -129,6 +144,15 @@ open class BrowseSourcePresenter( } } filtersChanged = false + + runBlocking { view?.savedSearches = loadSearches() } + + getSavedSearch.subscribeAllBySourceId(sourceId) + .map { it.applyAllSave(source.getFilterList()) } + .onEach { + withUIContext { view?.savedSearches = it } + } + .launchIn(presenterScope) } } @@ -360,4 +384,33 @@ open class BrowseSourcePresenter( } } } + + fun saveSearch(name: String, query: String, filters: FilterList) { + presenterScope.launchNonCancellableIO { + insertSavedSearch.await( + sourceId, + name, + query, + try { + Json.encodeToString(filterSerializer.serialize(filters)) + } catch (e: Exception) { + "[]" + }, + ) + } + } + + fun deleteSearch(searchId: Long) { + presenterScope.launchNonCancellableIO { + deleteSavedSearch.await(searchId) + } + } + + suspend fun loadSearch(id: Long): SavedSearch? { + return getSavedSearch.awaitById(id)?.applySave(source.getFilterList()) + } + + suspend fun loadSearches(): List { + return getSavedSearch.awaitAllBySourceId(sourceId).applyAllSave(source.getFilterList()) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchExtensions.kt new file mode 100644 index 0000000000..d07c2f9430 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchExtensions.kt @@ -0,0 +1,45 @@ +package eu.kanade.tachiyomi.ui.source.browse + +import eu.kanade.tachiyomi.source.model.FilterList +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonArray +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import yokai.domain.source.browse.filter.FilterSerializer +import yokai.domain.source.browse.filter.models.RawSavedSearch +import yokai.domain.source.browse.filter.models.SavedSearch + +fun RawSavedSearch.applySave( + originalFilters: FilterList, + json: Json = Injekt.get(), + filterSerializer: FilterSerializer = Injekt.get(), +): SavedSearch { + val rt = SavedSearch( + id = this.id, + name = this.name, + query = this.query.orEmpty(), + filters = null, + ) + if (filtersJson == null) { + return rt + } + + val filters = try { + json.decodeFromString(filtersJson!!) + } catch (e: Exception) { + null + } ?: return rt + + try { + filterSerializer.deserialize(originalFilters, filters) + return rt.copy(filters = originalFilters) + } catch (e: Exception) { + return rt + } +} + +fun List.applyAllSave( + originalFilters: FilterList, + json: Json = Injekt.get(), + filterSerializer: FilterSerializer = Injekt.get(), +) = this.map { it.applySave(originalFilters, json, filterSerializer) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchesAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchesAdapter.kt new file mode 100644 index 0000000000..5dfca7f047 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SavedSearchesAdapter.kt @@ -0,0 +1,93 @@ +package eu.kanade.tachiyomi.ui.source.browse + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.ElevatedSuggestionChip +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SuggestionChipDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.unit.dp +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import eu.kanade.tachiyomi.databinding.SourceFilterSheetSavedSearchBinding +import yokai.domain.source.browse.filter.models.SavedSearch +import yokai.presentation.theme.YokaiTheme + +class SavedSearchesAdapter( + val searches: () -> List, + val onSavedSearchClicked: (Long) -> Unit, + val onDeleteSavedSearchClicked: (Long) -> Unit, +) : + RecyclerView.Adapter() { + + private lateinit var binding: SourceFilterSheetSavedSearchBinding + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SavedSearchesViewHolder { + binding = SourceFilterSheetSavedSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return SavedSearchesViewHolder(binding.root) + } + + override fun getItemCount(): Int = 1 + + override fun onBindViewHolder(holder: SavedSearchesViewHolder, position: Int) { + holder.bind() + } + + inner class SavedSearchesViewHolder(view: View) : RecyclerView.ViewHolder(view) { + fun bind() { + binding.savedSearches.setContent { + YokaiTheme { + Content() + } + } + binding.savedSearches.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool) + } + + @Composable + fun Content() { + binding.savedSearchesTitle.isVisible = searches().isNotEmpty() + + FlowRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + searches().forEach { search -> + val inputChipInteractionSource = remember { MutableInteractionSource() } + Box { + ElevatedSuggestionChip( + label = { Text(search.name) }, + onClick = { }, + interactionSource = inputChipInteractionSource, + colors = SuggestionChipDefaults.elevatedSuggestionChipColors().copy( + containerColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.4f), + labelColor = MaterialTheme.colorScheme.onSurface, + ), + ) + // Workaround to add long click to chips + Box( + modifier = Modifier + .matchParentSize() + .combinedClickable( + onLongClick = { onDeleteSavedSearchClicked(search.id) }, + onClick = { onSavedSearchClicked(search.id) }, + interactionSource = inputChipInteractionSource, + indication = null, + ) + ) + } + } + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt index 9200f41ca9..2eb00a48df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/SourceFilterSheet.kt @@ -11,6 +11,7 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat.Type.systemBars import androidx.core.view.updateLayoutParams import androidx.core.view.updatePaddingRelative +import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetBehavior import eu.davidea.flexibleadapter.FlexibleAdapter @@ -22,27 +23,37 @@ import eu.kanade.tachiyomi.util.view.checkHeightThen import eu.kanade.tachiyomi.util.view.collapse import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsetsCompat import eu.kanade.tachiyomi.widget.E2EBottomSheetDialog +import yokai.domain.source.browse.filter.models.SavedSearch import yokai.presentation.component.recyclerview.VertPaddingDecoration import android.R as AR -class SourceFilterSheet(val activity: Activity) : - E2EBottomSheetDialog(activity) { - - private var filterChanged = true +class SourceFilterSheet( + val activity: Activity, + searches: () -> List = { emptyList() }, + val onSearchClicked: () -> Unit, + val onResetClicked: () -> Unit, + val onSaveClicked: () -> Unit, + val onSavedSearchClicked: (Long) -> Unit, + val onDeleteSavedSearchClicked: (Long) -> Unit, +) : E2EBottomSheetDialog(activity) { val adapter: FlexibleAdapter> = FlexibleAdapter>(null) .setDisplayHeadersAtStartUp(true) - var onSearchClicked = {} - - var onResetClicked = {} - override var recyclerView: RecyclerView? = binding.filtersRecycler override fun createBinding(inflater: LayoutInflater) = SourceFilterSheetBinding.inflate(inflater) + + private val savedSearchesAdapter = SavedSearchesAdapter( + searches = searches, + onSavedSearchClicked = onSavedSearchClicked, + onDeleteSavedSearchClicked = onDeleteSavedSearchClicked, + ) + init { binding.searchBtn.setOnClickListener { dismiss() } binding.resetBtn.setOnClickListener { onResetClicked() } + binding.saveBtn.setOnClickListener { onSaveClicked() } sheetBehavior.peekHeight = 450.dpToPx sheetBehavior.collapse() @@ -54,9 +65,7 @@ class SourceFilterSheet(val activity: Activity) : binding.cardView.doOnApplyWindowInsetsCompat { _, insets, _ -> binding.cardView.updateLayoutParams { val fullHeight = activity.window.decorView.height - matchConstraintMaxHeight = - fullHeight - insets.getInsets(systemBars()).top - - binding.titleLayout.height - 75.dpToPx + matchConstraintMaxHeight = fullHeight - insets.getInsets(systemBars()).top - binding.titleLayout.height - 75.dpToPx } } @@ -85,7 +94,7 @@ class SourceFilterSheet(val activity: Activity) : }, ) - binding.filtersRecycler.viewTreeObserver.addOnScrollChangedListener { + recyclerView?.viewTreeObserver?.addOnScrollChangedListener { updateBottomButtons() } @@ -93,11 +102,13 @@ class SourceFilterSheet(val activity: Activity) : updateBottomButtons() } - binding.filtersRecycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) - binding.filtersRecycler.addItemDecoration(VertPaddingDecoration(12.dpToPx)) - binding.filtersRecycler.clipToPadding = false - binding.filtersRecycler.adapter = adapter - binding.filtersRecycler.setHasFixedSize(false) + recyclerView?.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) + recyclerView?.addItemDecoration(VertPaddingDecoration(12.dpToPx)) + recyclerView?.adapter = ConcatAdapter( + savedSearchesAdapter, + adapter, + ) + recyclerView?.setHasFixedSize(false) sheetBehavior.addBottomSheetCallback( object : BottomSheetBehavior.BottomSheetCallback() { @@ -112,7 +123,7 @@ class SourceFilterSheet(val activity: Activity) : ) } - fun setCardViewMax(insets: WindowInsetsCompat) { + private fun setCardViewMax(insets: WindowInsetsCompat) { val fullHeight = activity.window.decorView.height val newHeight = fullHeight - insets.getInsets(systemBars()).top - binding.titleLayout.height - 75.dpToPx @@ -126,8 +137,10 @@ class SourceFilterSheet(val activity: Activity) : override fun onStart() { super.onStart() sheetBehavior.collapse() + scrollToTop() // Force the sheet to scroll to the very top when it shows up updateBottomButtons() binding.root.post { + scrollToTop() // Force the sheet to scroll to the very top when it shows up updateBottomButtons() } } @@ -157,12 +170,14 @@ class SourceFilterSheet(val activity: Activity) : override fun dismiss() { super.dismiss() - if (filterChanged) { - onSearchClicked() - } + onSearchClicked() } fun setFilters(items: List>) { adapter.updateDataSet(items) } + + fun scrollToTop() { + recyclerView?.layoutManager?.scrollToPosition(0) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt index a855434180..59b9ccd9df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/MaterialAlertDialogExtensions.kt @@ -5,16 +5,20 @@ import android.content.DialogInterface import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.TextView import androidx.annotation.CheckResult -import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.AppCompatCheckedTextView +import androidx.core.content.getSystemService import androidx.core.view.isVisible +import androidx.core.widget.doAfterTextChanged import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder import dev.icerock.moko.resources.StringResource import eu.kanade.tachiyomi.databinding.CustomDialogTitleMessageBinding import eu.kanade.tachiyomi.databinding.DialogQuadstateBinding +import eu.kanade.tachiyomi.databinding.DialogTextInputBinding import eu.kanade.tachiyomi.widget.TriStateCheckBox import eu.kanade.tachiyomi.widget.materialdialogs.TriStateMultiChoiceDialogAdapter import eu.kanade.tachiyomi.widget.materialdialogs.TriStateMultiChoiceListener @@ -155,3 +159,23 @@ val DialogInterface.isPromptChecked: Boolean fun interface MaterialAlertDialogBuilderOnCheckClickListener { fun onClick(var1: DialogInterface?, var3: Boolean) } + +fun MaterialAlertDialogBuilder.setTextInput( + hint: String? = null, + prefill: String? = null, + onTextChanged: (String) -> Unit, +): MaterialAlertDialogBuilder { + val binding = DialogTextInputBinding.inflate(LayoutInflater.from(context)) + binding.textField.hint = hint + binding.textField.editText?.apply { + setText(prefill, TextView.BufferType.EDITABLE) + doAfterTextChanged { + onTextChanged(it?.toString() ?: "") + } + post { + requestFocusFromTouch() + context.getSystemService()?.showSoftInput(this, 0) + } + } + return setView(binding.root) +} diff --git a/app/src/main/java/yokai/core/di/DomainModule.kt b/app/src/main/java/yokai/core/di/DomainModule.kt index 31e98fc4fa..406e6457db 100644 --- a/app/src/main/java/yokai/core/di/DomainModule.kt +++ b/app/src/main/java/yokai/core/di/DomainModule.kt @@ -7,6 +7,7 @@ import yokai.data.extension.repo.ExtensionRepoRepositoryImpl import yokai.data.history.HistoryRepositoryImpl import yokai.data.library.custom.CustomMangaRepositoryImpl import yokai.data.manga.MangaRepositoryImpl +import yokai.data.source.browse.filter.SavedSearchRepositoryImpl import yokai.data.track.TrackRepositoryImpl import yokai.domain.category.CategoryRepository import yokai.domain.category.interactor.DeleteCategories @@ -42,6 +43,11 @@ import yokai.domain.manga.interactor.GetManga import yokai.domain.manga.interactor.InsertManga import yokai.domain.manga.interactor.UpdateManga import yokai.domain.recents.interactor.GetRecents +import yokai.domain.source.browse.filter.FilterSerializer +import yokai.domain.source.browse.filter.SavedSearchRepository +import yokai.domain.source.browse.filter.interactor.DeleteSavedSearch +import yokai.domain.source.browse.filter.interactor.GetSavedSearch +import yokai.domain.source.browse.filter.interactor.InsertSavedSearch import yokai.domain.track.TrackRepository import yokai.domain.track.interactor.DeleteTrack import yokai.domain.track.interactor.GetTrack @@ -95,4 +101,10 @@ fun domainModule() = module { factory { DeleteTrack(get()) } factory { GetTrack(get()) } factory { InsertTrack(get()) } + + single { SavedSearchRepositoryImpl(get()) } + factory { DeleteSavedSearch(get()) } + factory { GetSavedSearch(get()) } + factory { InsertSavedSearch(get()) } + factory { FilterSerializer() } } diff --git a/app/src/main/java/yokai/domain/source/browse/filter/FilterSerializer.kt b/app/src/main/java/yokai/domain/source/browse/filter/FilterSerializer.kt new file mode 100644 index 0000000000..ec7e66a12f --- /dev/null +++ b/app/src/main/java/yokai/domain/source/browse/filter/FilterSerializer.kt @@ -0,0 +1,94 @@ +package yokai.domain.source.browse.filter + +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.full.isSubclassOf +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.double +import kotlinx.serialization.json.float +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.long +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonObject + +class FilterSerializer { + private val serializers = listOf>( + HeaderSerializer(this), + SeparatorSerializer(this), + SelectSerializer(this), + TextSerializer(this), + CheckBoxSerializer(this), + TriStateSerializer(this), + GroupSerializer(this), + SortSerializer(this), + ) + + fun serialize(filters: FilterList) = buildJsonArray { + filters.filterIsInstance>().forEach { add(serialize(it)) } + } + + fun serialize(filter: Filter): JsonObject { + return serializers + .filterIsInstance>>() + .firstOrNull { filter::class.isSubclassOf(it.clazz) } + ?.let { serializer -> + buildJsonObject { + with(serializer) { serialize(filter) } + + serializer.mappings().forEach { + val res = it.second.get(filter) + putJsonObject(it.first) { + put(Serializer.TYPE, res?.javaClass?.name ?: "null") + put("value", res.toString()) + } + } + + put(Serializer.TYPE, serializer.type) + } + } ?: throw IllegalArgumentException("Cannot serialize this Filter object!") + } + + fun deserialize(filters: FilterList, json: JsonArray) { + filters.filterIsInstance>().zip(json).forEach { (filter, obj) -> + deserialize(filter, obj.jsonObject) + } + } + + fun deserialize(filter: Filter, json: JsonObject) { + val serializer = serializers + .filterIsInstance>>() + .firstOrNull { it.type == json[Serializer.TYPE]!!.jsonPrimitive.content } + ?: throw IllegalArgumentException("Cannot deserialize this type!") + + serializer.deserialize(json, filter) + + serializer.mappings().forEach { + if (it.second is KMutableProperty1) { + val valueObj = json[it.first]!!.jsonObject + val obj = valueObj["value"]!!.jsonPrimitive + val res: Any? = when (valueObj[Serializer.TYPE]!!.jsonPrimitive.content) { + java.lang.Integer::class.java.name -> obj.int + java.lang.Long::class.java.name -> obj.long + java.lang.Float::class.java.name -> obj.float + java.lang.Double::class.java.name -> obj.double + java.lang.String::class.java.name -> obj.content + java.lang.Boolean::class.java.name -> obj.boolean + java.lang.Byte::class.java.name -> obj.content.toByte() + java.lang.Short::class.java.name -> obj.content.toShort() + java.lang.Character::class.java.name -> obj.content[0] + "null" -> null + else -> throw IllegalArgumentException("Cannot deserialize this type!") + } + @Suppress("UNCHECKED_CAST") + (it.second as KMutableProperty1, in Any?>).set(filter, res) + } + } + } +} diff --git a/app/src/main/java/yokai/domain/source/browse/filter/FilterTypeSerializer.kt b/app/src/main/java/yokai/domain/source/browse/filter/FilterTypeSerializer.kt new file mode 100644 index 0000000000..c763e6ca4e --- /dev/null +++ b/app/src/main/java/yokai/domain/source/browse/filter/FilterTypeSerializer.kt @@ -0,0 +1,167 @@ +package yokai.domain.source.browse.filter + +import eu.kanade.tachiyomi.source.model.Filter +import kotlin.reflect.KClass +import kotlin.reflect.KProperty1 +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.add +import kotlinx.serialization.json.addAll +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray + +interface Serializer> { + fun JsonObjectBuilder.serialize(filter: T) {} + fun deserialize(json: JsonObject, filter: T) {} + + fun mappings(): List>> = emptyList() + + val serializer: FilterSerializer + val type: String + val clazz: KClass + + companion object { + const val TYPE = "_type" + const val NAME = "name" + const val STATE = "state" + } +} + +class HeaderSerializer(override val serializer: FilterSerializer) : Serializer { + override val type = "HEADER" + override val clazz = Filter.Header::class + + override fun mappings() = listOf( + Serializer.NAME to Filter.Header::name, + ) +} + +class SeparatorSerializer(override val serializer: FilterSerializer) : Serializer { + override val type = "SEPARATOR" + override val clazz = Filter.Separator::class + + override fun mappings() = listOf( + Serializer.NAME to Filter.Separator::name, + ) +} + +class SelectSerializer(override val serializer: FilterSerializer) : Serializer> { + override val type = "SELECT" + override val clazz = Filter.Select::class + + override fun JsonObjectBuilder.serialize(filter: Filter.Select) { + putJsonArray("values") { + addAll(filter.values.map { it.toString() }) + } + } + + override fun mappings() = listOf( + Serializer.NAME to Filter.Select::name, + Serializer.STATE to Filter.Select::state, + ) +} + +class TextSerializer(override val serializer: FilterSerializer) : Serializer { + override val type = "TEXT" + override val clazz = Filter.Text::class + + override fun mappings() = listOf( + Serializer.NAME to Filter.Text::name, + Serializer.STATE to Filter.Text::state, + ) +} + +class CheckBoxSerializer(override val serializer: FilterSerializer) : Serializer { + override val type = "CHECKBOX" + override val clazz = Filter.CheckBox::class + + override fun mappings() = listOf( + Serializer.NAME to Filter.CheckBox::name, + Serializer.STATE to Filter.CheckBox::state, + ) +} + +class TriStateSerializer(override val serializer: FilterSerializer) : Serializer { + override val type = "TRI_STATE" + override val clazz = Filter.TriState::class + + override fun mappings() = listOf( + Serializer.NAME to Filter.TriState::name, + Serializer.STATE to Filter.TriState::state, + ) +} + +class GroupSerializer(override val serializer: FilterSerializer) : Serializer> { + override val type = "GROUP" + override val clazz = Filter.Group::class + + override fun JsonObjectBuilder.serialize(filter: Filter.Group) { + putJsonArray(Serializer.STATE) { + filter.state.forEach { state -> + @Suppress("UNCHECKED_CAST") + add((state as? Filter)?.let { serializer.serialize(it) } ?: JsonNull) + } + } + } + + override fun deserialize(json: JsonObject, filter: Filter.Group) { + json[Serializer.STATE]!!.jsonArray.forEachIndexed { index, element -> + if (element == JsonNull) return@forEachIndexed + + @Suppress("UNCHECKED_CAST") + serializer.deserialize(filter.state[index] as Filter, element.jsonObject) + } + } + + override fun mappings() = listOf( + Serializer.NAME to Filter.Group::name, + ) +} + +class SortSerializer(override val serializer: FilterSerializer) : Serializer { + override val type = "SORT" + override val clazz = Filter.Sort::class + + override fun JsonObjectBuilder.serialize(filter: Filter.Sort) { + putJsonArray(VALUES) { + filter.values.forEach { add(it) } + } + + put( + Serializer.STATE, + filter.state?.let { (index, ascending) -> + buildJsonObject { + put(STATE_INDEX, index) + put(STATE_ASCENDING, ascending) + } + } ?: JsonNull, + ) + } + + override fun deserialize(json: JsonObject, filter: Filter.Sort) { + filter.state = (json[Serializer.STATE] as? JsonObject)?.let { + Filter.Sort.Selection( + it[STATE_INDEX]!!.jsonPrimitive.int, + it[STATE_ASCENDING]!!.jsonPrimitive.boolean, + ) + } + } + + override fun mappings() = listOf( + Pair(Serializer.NAME, Filter.Sort::name), + ) + + companion object { + const val VALUES = "values" + + const val STATE_INDEX = "index" + const val STATE_ASCENDING = "ascending" + } +} diff --git a/app/src/main/java/yokai/domain/source/browse/filter/interactor/DeleteSavedSearch.kt b/app/src/main/java/yokai/domain/source/browse/filter/interactor/DeleteSavedSearch.kt new file mode 100644 index 0000000000..6c460bca89 --- /dev/null +++ b/app/src/main/java/yokai/domain/source/browse/filter/interactor/DeleteSavedSearch.kt @@ -0,0 +1,9 @@ +package yokai.domain.source.browse.filter.interactor + +import yokai.domain.source.browse.filter.SavedSearchRepository + +class DeleteSavedSearch( + private val repository: SavedSearchRepository, +) { + suspend fun await(searchId: Long) = repository.deleteById(searchId) +} diff --git a/app/src/main/java/yokai/domain/source/browse/filter/interactor/GetSavedSearch.kt b/app/src/main/java/yokai/domain/source/browse/filter/interactor/GetSavedSearch.kt new file mode 100644 index 0000000000..da8a8fd5c6 --- /dev/null +++ b/app/src/main/java/yokai/domain/source/browse/filter/interactor/GetSavedSearch.kt @@ -0,0 +1,13 @@ +package yokai.domain.source.browse.filter.interactor + +import yokai.domain.source.browse.filter.SavedSearchRepository + +class GetSavedSearch( + private val repository: SavedSearchRepository, +) { + suspend fun awaitAll() = repository.findAll() + suspend fun awaitAllBySourceId(sourceId: Long) = repository.findAllBySourceId(sourceId) + fun subscribeAllBySourceId(sourceId: Long) = repository.subscribeAllBySourceId(sourceId) + suspend fun awaitBySourceIdAndName(sourceId: Long, name: String) = repository.findOneBySourceIdAndName(sourceId, name) + suspend fun awaitById(id: Long) = repository.findById(id) +} diff --git a/app/src/main/java/yokai/domain/source/browse/filter/interactor/InsertSavedSearch.kt b/app/src/main/java/yokai/domain/source/browse/filter/interactor/InsertSavedSearch.kt new file mode 100644 index 0000000000..eb8e6ee08d --- /dev/null +++ b/app/src/main/java/yokai/domain/source/browse/filter/interactor/InsertSavedSearch.kt @@ -0,0 +1,9 @@ +package yokai.domain.source.browse.filter.interactor + +import yokai.domain.source.browse.filter.SavedSearchRepository + +class InsertSavedSearch( + private val repository: SavedSearchRepository, +) { + suspend fun await(sourceId: Long, name: String, query: String?, filtersJson: String?) = repository.insert(sourceId, name, query, filtersJson) +} diff --git a/app/src/main/java/yokai/domain/source/browse/filter/models/SavedSearch.kt b/app/src/main/java/yokai/domain/source/browse/filter/models/SavedSearch.kt new file mode 100644 index 0000000000..d18e8ad2c2 --- /dev/null +++ b/app/src/main/java/yokai/domain/source/browse/filter/models/SavedSearch.kt @@ -0,0 +1,10 @@ +package yokai.domain.source.browse.filter.models + +import eu.kanade.tachiyomi.source.model.FilterList + +data class SavedSearch( + val id: Long, + val name: String, + val query: String, + val filters: FilterList?, +) diff --git a/app/src/main/res/layout/browse_source_controller.xml b/app/src/main/res/layout/browse_source_controller.xml index 699d8109ca..31e054b8fc 100644 --- a/app/src/main/res/layout/browse_source_controller.xml +++ b/app/src/main/res/layout/browse_source_controller.xml @@ -25,11 +25,12 @@ tools:visibility="visible"/> - + + + + + + + + + + diff --git a/app/src/main/res/layout/source_filter_sheet.xml b/app/src/main/res/layout/source_filter_sheet.xml index 54bc53f241..a8e76eb145 100644 --- a/app/src/main/res/layout/source_filter_sheet.xml +++ b/app/src/main/res/layout/source_filter_sheet.xml @@ -1,12 +1,13 @@ - + + + + app:layout_constraintWidth_min="120dp" /> + + + + + + + diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 6f17a84c4b..4d826d7242 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -10,6 +10,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { + api(projects.domain) api(libs.bundles.db) } } diff --git a/data/src/commonMain/kotlin/yokai/data/source/browse/filter/SavedSearchRepositoryImpl.kt b/data/src/commonMain/kotlin/yokai/data/source/browse/filter/SavedSearchRepositoryImpl.kt new file mode 100644 index 0000000000..84696c3ad7 --- /dev/null +++ b/data/src/commonMain/kotlin/yokai/data/source/browse/filter/SavedSearchRepositoryImpl.kt @@ -0,0 +1,37 @@ +package yokai.data.source.browse.filter + +import yokai.data.DatabaseHandler +import yokai.domain.source.browse.filter.SavedSearchRepository +import yokai.domain.source.browse.filter.models.RawSavedSearch + +class SavedSearchRepositoryImpl(private val handler: DatabaseHandler) : SavedSearchRepository { + override suspend fun findAll(): List = handler.awaitList { + saved_searchQueries.findAll(RawSavedSearch::mapper) + } + + override fun subscribeAllBySourceId(sourceId: Long) = handler.subscribeToList { + saved_searchQueries.findBySourceId(sourceId, RawSavedSearch::mapper) + } + + override suspend fun findAllBySourceId(sourceId: Long) = handler.awaitList { + saved_searchQueries.findBySourceId(sourceId, RawSavedSearch::mapper) + } + + override suspend fun findOneBySourceIdAndName(sourceId: Long, name: String): RawSavedSearch? = handler.awaitFirstOrNull { + saved_searchQueries.findBySourceIdAndName(sourceId, name, RawSavedSearch::mapper) + } + + override suspend fun findById(id: Long): RawSavedSearch? = handler.awaitFirstOrNull { + saved_searchQueries.findById(id, RawSavedSearch::mapper) + } + + override suspend fun deleteById(id: Long) = handler.await { + saved_searchQueries.deleteById(id) + } + + override suspend fun insert(sourceId: Long, name: String, query: String?, filtersJson: String?) = + handler.awaitOneOrNullExecutable(inTransaction = true) { + saved_searchQueries.insert(sourceId, name, query, filtersJson) + saved_searchQueries.selectLastInsertedRowId() + } +} diff --git a/data/src/commonMain/sqldelight/tachiyomi/data/saved_search.sq b/data/src/commonMain/sqldelight/tachiyomi/data/saved_search.sq new file mode 100644 index 0000000000..8a8e21252c --- /dev/null +++ b/data/src/commonMain/sqldelight/tachiyomi/data/saved_search.sq @@ -0,0 +1,35 @@ +CREATE TABLE saved_search( + -- TODO: Migrate the other tables to use 'id' instead of '_id' + id INTEGER NOT NULL PRIMARY KEY, + source_id INTEGER NOT NULL, + name TEXT NOT NULL, + query TEXT, + filters_json TEXT, + UNIQUE (source_id, name) +); + +findAll: +SELECT * FROM saved_search +ORDER BY name COLLATE NOCASE ASC; + +findBySourceId: +SELECT * FROM saved_search WHERE source_id = :sourceId +ORDER BY name COLLATE NOCASE ASC; + +findBySourceIdAndName: +SELECT * FROM saved_search WHERE source_id = :sourceId AND name = :name +ORDER BY name COLLATE NOCASE ASC; + +findById: +SELECT * FROM saved_search WHERE id = :id +ORDER BY name COLLATE NOCASE ASC; + +deleteById: +DELETE FROM saved_search WHERE id = :id; + +insert: +INSERT INTO saved_search(source_id, name, query, filters_json) +VALUES (:sourceId, :name, :query, :filtersJson); + +selectLastInsertedRowId: +SELECT last_insert_rowid(); diff --git a/data/src/commonMain/sqldelight/tachiyomi/migrations/28.sqm b/data/src/commonMain/sqldelight/tachiyomi/migrations/28.sqm new file mode 100644 index 0000000000..4a1f0540da --- /dev/null +++ b/data/src/commonMain/sqldelight/tachiyomi/migrations/28.sqm @@ -0,0 +1,8 @@ +CREATE TABLE saved_search( + id INTEGER NOT NULL PRIMARY KEY, + source_id INTEGER NOT NULL, + name TEXT NOT NULL, + query TEXT, + filters_json TEXT, + UNIQUE (source_id, name) +); diff --git a/domain/src/commonMain/kotlin/yokai/domain/source/browse/filter/SavedSearchRepository.kt b/domain/src/commonMain/kotlin/yokai/domain/source/browse/filter/SavedSearchRepository.kt new file mode 100644 index 0000000000..abce233d30 --- /dev/null +++ b/domain/src/commonMain/kotlin/yokai/domain/source/browse/filter/SavedSearchRepository.kt @@ -0,0 +1,14 @@ +package yokai.domain.source.browse.filter + +import kotlinx.coroutines.flow.Flow +import yokai.domain.source.browse.filter.models.RawSavedSearch + +interface SavedSearchRepository { + suspend fun findAll(): List + fun subscribeAllBySourceId(sourceId: Long): Flow> + suspend fun findAllBySourceId(sourceId: Long): List + suspend fun findOneBySourceIdAndName(sourceId: Long, name: String): RawSavedSearch? + suspend fun findById(id: Long): RawSavedSearch? + suspend fun deleteById(id: Long) + suspend fun insert(sourceId: Long, name: String, query: String?, filtersJson: String?): Long? +} diff --git a/domain/src/commonMain/kotlin/yokai/domain/source/browse/filter/models/RawSavedSearch.kt b/domain/src/commonMain/kotlin/yokai/domain/source/browse/filter/models/RawSavedSearch.kt new file mode 100644 index 0000000000..b1ca1bdd2a --- /dev/null +++ b/domain/src/commonMain/kotlin/yokai/domain/source/browse/filter/models/RawSavedSearch.kt @@ -0,0 +1,25 @@ +package yokai.domain.source.browse.filter.models + +data class RawSavedSearch( + val id: Long, + val sourceId: Long, + val name: String, + val query: String?, + val filtersJson: String?, +) { + companion object { + fun mapper( + id: Long, + sourceId: Long, + name: String, + query: String?, + filtersJson: String?, + ) = RawSavedSearch( + id = id, + sourceId = sourceId, + name = name, + query = query, + filtersJson = filtersJson, + ) + } +} diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 7198556527..32a846286a 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -1153,6 +1153,12 @@ Default orientation Orientation Save + Saved searches + Save current search query? + Save search name + Invalid saved search name + Delete this saved search query? + Are you sure you wish to delete this saved search query: \'%1$s\'? Search Search %1$s Select all From 8be33e0f81273d30678fc5f258ae238bc434c77a Mon Sep 17 00:00:00 2001 From: Lee Shuen Fei <109432084+LeeSF03@users.noreply.github.com> Date: Tue, 15 Apr 2025 07:05:15 +0700 Subject: [PATCH 116/181] feat: Display the number of filtered manga in each category's header when searching in library (#387) --- .../tachiyomi/ui/library/LibraryCategoryAdapter.kt | 1 + .../tachiyomi/ui/library/LibraryHeaderHolder.kt | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt index 4bd14503bd..dc63b2db5e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt @@ -173,6 +173,7 @@ class LibraryCategoryAdapter(val controller: LibraryController?) : } isLongPressDragEnabled = libraryListener?.canDrag() == true && s.isNullOrBlank() setItemsPerCategoryMap() + notifyDataSetChanged() } private fun getFirstLetter(name: String): String { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt index 435c209366..85be9b426a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHeaderHolder.kt @@ -186,7 +186,17 @@ class LibraryHeaderHolder(val view: View, val adapter: LibraryCategoryAdapter) : binding.categoryTitle.text = categoryName + if (adapter.showNumber) { - " (${adapter.itemsPerCategory[item.catId]})" + val filteredCount = adapter.currentItems.count { + it is LibraryMangaItem && it.header?.catId == item.catId + } + val totalCount = adapter.itemsPerCategory[item.catId] ?: 0 + val searchText = adapter.getFilter(String::class.java) + var countText = if (searchText.isNullOrBlank()) { + " ($totalCount)" + } else { + " ($filteredCount/$totalCount)" + } + countText } else { "" } if (category.sourceId != null) { val icon = adapter.sourceManager.get(category.sourceId!!)?.icon() From 271e4400146f146903ddce98c3068ae29e0433cd Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Thu, 17 Apr 2025 17:09:00 +0700 Subject: [PATCH 117/181] fix: Prevent potential "Comparison method violates its general contract!" crash --- CHANGELOG.md | 2 ++ .../tachiyomi/ui/library/LibraryPresenter.kt | 15 +++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be74d403d8..d63fcc879b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co ### Changes - Temporarily disable log file +- Categories' header now show filtered count when you search the library (If you have "Show number of items" enabled) ### Fixes - Allow users to bypass onboarding's permission step if Shizuku is installed @@ -23,6 +24,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Fix not fully loaded entries can't be selected on Library page - Fix certain Infinix devices being unable to use any "Open link in browser" actions, including tracker setup (@MajorTanya) - Fix source filter bottom sheet unable to be fully scrolled to the bottom +- Prevent potential "Comparison method violates its general contract!" crash ### Translation - Update translations from Weblate diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index e5d8db30c1..e82f372dcc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -660,6 +660,9 @@ class LibraryPresenter( * @param itemList the map to sort. */ private fun LibraryMap.applySort(): LibraryMap { + // Making sure `allCategories` is stable for `.sort()` + val categoryOrderMap = allCategories.associate { it.id to it.order } + val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 -> val category = i1.header.category val compare = when { @@ -688,12 +691,8 @@ class LibraryPresenter( LibrarySort.DateAdded -> i2.manga.manga.date_added.compareTo(i1.manga.manga.date_added) LibrarySort.DragAndDrop -> { if (category.isDynamic) { - val category1 = - allCategories.find { i1.manga.category == it.id }?.order - ?: 0 - val category2 = - allCategories.find { i2.manga.category == it.id }?.order - ?: 0 + val category1 = categoryOrderMap[i1.manga.category] ?: 0 + val category2 = categoryOrderMap[i2.manga.category] ?: 0 category1.compareTo(category2) } else { sortAlphabetical(i1, i2) @@ -727,7 +726,7 @@ class LibraryPresenter( } return this.mapValues { (category, values) -> - // Making sure category has valid sort + // Making sure category has valid sort before doing the actual sorting if (category.mangaOrder.isEmpty() && category.mangaSort == null) { category.changeSortTo(preferences.librarySortingMode().get()) if (category.id == 0) { @@ -804,7 +803,7 @@ class LibraryPresenter( preferences.groupLibraryBy().changes(), preferences.showAllCategories().changes(), - + preferences.librarySortingMode().changes(), preferences.librarySortingAscending().changes(), From ea634a5ce3b149d27ed977a6162f4285312ba418 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 18 Apr 2025 12:07:46 +0700 Subject: [PATCH 118/181] chore: Bump version to v1.10.0 [skip ci] Changing the version scheme to: - Major -> Basically a rewrite - Minor -> Feature releases / big changes - Macro -> Bug fixes --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index deb59dc23a..44eaebc446 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,7 +26,7 @@ fun runCommand(command: String): String { } @Suppress("PropertyName") -val _versionName = "1.9.8" +val _versionName = "1.10.0" val betaCount by lazy { val betaTags = runCommand("git tag -l --sort=refname v${_versionName}-b*") From 66241774dc212c4e25e522b9b22894b8da1c28e6 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 5 May 2025 06:05:36 +0700 Subject: [PATCH 119/181] chore(deps): Update dependency gradle to v8.12 --- CHANGELOG.md | 1 + gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d63fcc879b..2cc430487b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - LibraryItem abstraction to make it easier to manage - LibraryManga no longer extend MangaImpl - Update dependency androidx.compose:compose-bom to v2025.01.00 +- Update dependency gradle to v8.12 ## [1.9.7] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2847c8200..cea7a793a8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 94c314559bec031c569cba5327bf1029804e3cf3 Mon Sep 17 00:00:00 2001 From: Hiirbaf <42479509+Hiirbaf@users.noreply.github.com> Date: Sun, 4 May 2025 20:46:29 -0300 Subject: [PATCH 120/181] feat: Enable/Disable Sources Swipe (#396) * Update SourceItem.kt * Update SettingsBrowseController.kt 1 * Update SettingsBrowseController.kt * Update SettingsBrowseController.kt * Update strings.xml * Update SettingsBrowseController.kt * Update UiPreferences.kt * Update SettingsBrowseController.kt * Update SourceItem.kt --- .../setting/controllers/SettingsBrowseController.kt | 12 ++++++++++++ .../java/eu/kanade/tachiyomi/ui/source/SourceItem.kt | 5 ++++- app/src/main/java/yokai/domain/ui/UiPreferences.kt | 2 ++ i18n/src/commonMain/moko-resources/base/strings.xml | 1 + 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsBrowseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsBrowseController.kt index 56f4d08b87..7abbb51ba3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsBrowseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsBrowseController.kt @@ -8,6 +8,7 @@ import androidx.preference.PreferenceScreen import androidx.preference.SwitchPreferenceCompat import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R +import yokai.domain.ui.UiPreferences import yokai.i18n.MR import yokai.util.lang.getString import dev.icerock.moko.resources.compose.stringResource @@ -45,6 +46,8 @@ class SettingsBrowseController : SettingsLegacyController() { val sourceManager: SourceManager by injectLazy() var updatedExtNotifPref: SwitchPreferenceCompat? = null + private val uiPreferences: UiPreferences by injectLazy() + override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { titleRes = MR.strings.browse @@ -198,6 +201,15 @@ class SettingsBrowseController : SettingsLegacyController() { infoPreference(MR.strings.you_can_migrate_in_library) } + + preferenceCategory { + titleRes = MR.strings.sources + + switchPreference { + bindTo(uiPreferences.enableSourceSwipeAction()) + titleRes = MR.strings.enable_source_swipe_action + } + } preferenceCategory { titleRes = MR.strings.nsfw_sources diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt index 14d1d58722..fbf6cd8ca3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt @@ -11,6 +11,9 @@ import yokai.util.lang.getString import dev.icerock.moko.resources.compose.stringResource import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.LocalSource +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import yokai.domain.ui.UiPreferences /** * Item that contains source information. @@ -29,7 +32,7 @@ class SourceItem(val source: CatalogueSource, header: LangItem? = null, val isPi } override fun isSwipeable(): Boolean { - return source.id != LocalSource.ID && header != null && header.code != SourcePresenter.LAST_USED_KEY + return Injekt.get().enableSourceSwipeAction().get() && source.id != LocalSource.ID && header != null && header.code != SourcePresenter.LAST_USED_KEY } /** diff --git a/app/src/main/java/yokai/domain/ui/UiPreferences.kt b/app/src/main/java/yokai/domain/ui/UiPreferences.kt index 3d35eee75a..1e73ad2eea 100644 --- a/app/src/main/java/yokai/domain/ui/UiPreferences.kt +++ b/app/src/main/java/yokai/domain/ui/UiPreferences.kt @@ -11,4 +11,6 @@ class UiPreferences(private val preferenceStore: PreferenceStore) { fun uniformGrid() = preferenceStore.getBoolean(PreferenceKeys.uniformGrid, true) fun enableChapterSwipeAction() = preferenceStore.getBoolean("enable_chapter_swipe_action", true) + + fun enableSourceSwipeAction() = preferenceStore.getBoolean("enable_source_swipe_action", true) } diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 32a846286a..ef4ade4837 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -884,6 +884,7 @@ You can also migrate by selecting entries in your library Source migration guide + Enable source swipe action NSFW (18+) sources Show in sources and extensions lists This does not prevent unofficial or potentially incorrectly flagged extensions from surfacing NSFW (18+) content within the app. From 65639391b781f538adf1b6fac6648ed63b4cc6d8 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 5 May 2025 06:49:29 +0700 Subject: [PATCH 121/181] docs: Sync changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cc430487b..9cb96b09bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co ### Additions - Add random library sort - Add the ability to save search queries +- Add toggle to enable/disable hide source on swipe ### Changes - Temporarily disable log file From f74662c0f350ffb7b427a6959a58aeb7160e00f4 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 5 May 2025 07:01:51 +0700 Subject: [PATCH 122/181] docs: Add missing credits [skip ci] --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cb96b09bc..a15f6e2c86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,11 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co ### Additions - Add random library sort - Add the ability to save search queries -- Add toggle to enable/disable hide source on swipe +- Add toggle to enable/disable hide source on swipe (@Hiirbaf) ### Changes - Temporarily disable log file -- Categories' header now show filtered count when you search the library (If you have "Show number of items" enabled) +- Categories' header now show filtered count when you search the library when you have "Show number of items" enabled (@LeeSF03) ### Fixes - Allow users to bypass onboarding's permission step if Shizuku is installed From f362b0bda0ab943b32ab533f0b49083d5708fd5d Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 9 May 2025 10:44:30 +0700 Subject: [PATCH 123/181] fix: Fix saved search not restoring Filter.Group properly This happened when Filter.Group ordering doesn't match the old ordering. --- .../browse/filter/FilterTypeSerializer.kt | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/yokai/domain/source/browse/filter/FilterTypeSerializer.kt b/app/src/main/java/yokai/domain/source/browse/filter/FilterTypeSerializer.kt index c763e6ca4e..817765b643 100644 --- a/app/src/main/java/yokai/domain/source/browse/filter/FilterTypeSerializer.kt +++ b/app/src/main/java/yokai/domain/source/browse/filter/FilterTypeSerializer.kt @@ -16,6 +16,7 @@ import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlinx.serialization.json.putJsonArray +import kotlinx.serialization.json.putJsonObject interface Serializer> { fun JsonObjectBuilder.serialize(filter: T) {} @@ -103,26 +104,45 @@ class GroupSerializer(override val serializer: FilterSerializer) : Serializer) { - putJsonArray(Serializer.STATE) { - filter.state.forEach { state -> - @Suppress("UNCHECKED_CAST") - add((state as? Filter)?.let { serializer.serialize(it) } ?: JsonNull) + putJsonObject(VALUES) { + filter.state.forEach { item -> + @Suppress("UNCHECKED_CAST", "SafeCastWithReturn") + item as? Filter ?: return@forEach + // Assuming `item.name` is unique, not sure if it is tho... + put(item.name, serializer.serialize(item)) } } } override fun deserialize(json: JsonObject, filter: Filter.Group) { - json[Serializer.STATE]!!.jsonArray.forEachIndexed { index, element -> - if (element == JsonNull) return@forEachIndexed + val values = json[VALUES]?.jsonObject + if (values == null) { + // TODO: Delete later + json[Serializer.STATE]?.jsonArray?.forEachIndexed { index, element -> + if (element == JsonNull) return@forEachIndexed - @Suppress("UNCHECKED_CAST") - serializer.deserialize(filter.state[index] as Filter, element.jsonObject) + @Suppress("UNCHECKED_CAST") + serializer.deserialize(filter.state[index] as Filter, element.jsonObject) + } + return + } + + filter.state.forEach { item -> + @Suppress("UNCHECKED_CAST", "SafeCastWithReturn") + item as? Filter ?: return@forEach + + val itemJson = values[item.name]?.jsonObject ?: return@forEach + serializer.deserialize(item, itemJson) } } override fun mappings() = listOf( Serializer.NAME to Filter.Group::name, ) + + companion object { + const val VALUES = "values" + } } class SortSerializer(override val serializer: FilterSerializer) : Serializer { From 96f88d5e907d3ef7b5aa72011076696f3e0e70c3 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 9 May 2025 11:17:58 +0700 Subject: [PATCH 124/181] docs: Sync changelog [skip ci] --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a15f6e2c86..95e7865792 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,11 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Update dependency androidx.compose:compose-bom to v2025.01.00 - Update dependency gradle to v8.12 +## [1.9.7.1] + +### Fixes +- Prevent `Comparison method violates its general contract!` crashes + ## [1.9.7] ### Changes From f0354541500f53d0983635f8aa2b9a5b46f9dcb2 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 9 May 2025 11:18:55 +0700 Subject: [PATCH 125/181] chore: Sync project [skip ci] --- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/issue_report.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index bc57d11bf2..c4388edd69 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -35,7 +35,7 @@ body: required: true - label: If this is an issue with an extension, or a request for an extension, I should be contacting the extensions repository's maintainer/support for help. required: true - - label: I have updated the app to version **[1.9.7](https://github.com/null2264/yokai/releases/latest)**. + - label: I have updated the app to version **[1.9.7.1](https://github.com/null2264/yokai/releases/latest)**. required: true - label: I have checked through the app settings for my feature. required: true diff --git a/.github/ISSUE_TEMPLATE/issue_report.yml b/.github/ISSUE_TEMPLATE/issue_report.yml index 38e96918aa..5fc99826f9 100644 --- a/.github/ISSUE_TEMPLATE/issue_report.yml +++ b/.github/ISSUE_TEMPLATE/issue_report.yml @@ -100,7 +100,7 @@ body: required: true - label: I have tried the [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/). required: true - - label: I have updated the app to version **[1.9.7](https://github.com/null2264/yokai/releases/latest)**. + - label: I have updated the app to version **[1.9.7.1](https://github.com/null2264/yokai/releases/latest)**. required: true - label: I have updated all installed extensions. required: true From 2b2e0491e8cd4af90ee6d3bf41268a018f7dc4dd Mon Sep 17 00:00:00 2001 From: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> Date: Sun, 11 May 2025 17:52:43 +0500 Subject: [PATCH 126/181] fix: staggered grid cover being squashed for local source (#398) * fix: cover ratio NaN for local source * unused * Update CHANGELOG.md --- CHANGELOG.md | 3 ++- .../eu/kanade/tachiyomi/util/manga/MangaCoverMetadata.kt | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e7865792..f01f3eb4ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,8 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Fix certain Infinix devices being unable to use any "Open link in browser" actions, including tracker setup (@MajorTanya) - Fix source filter bottom sheet unable to be fully scrolled to the bottom - Prevent potential "Comparison method violates its general contract!" crash - +- Fix staggered grid cover being squashed for local source + ### Translation - Update translations from Weblate diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaCoverMetadata.kt b/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaCoverMetadata.kt index 41ed89f945..d8be785576 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaCoverMetadata.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaCoverMetadata.kt @@ -66,7 +66,12 @@ object MangaCoverMetadata { } else { options.inSampleSize = 4 } - val bitmap = BitmapFactory.decodeFile(file.filePath, options) + val bitmap = try { + val stream = file.openInputStream() + BitmapFactory.decodeStream(stream, null, options) + } catch (_: Throwable) { + null + } if (bitmap != null) { Palette.from(bitmap).generate { palette -> if (isInLibrary) { From 0e0e865cb93dd9a26a488d32c510aa8133e68f23 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 11 May 2025 19:54:26 +0700 Subject: [PATCH 127/181] docs(CHANGELOG): Add missing credit [skip ci] --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f01f3eb4ea..2f1bb7cf46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,8 +26,8 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Fix certain Infinix devices being unable to use any "Open link in browser" actions, including tracker setup (@MajorTanya) - Fix source filter bottom sheet unable to be fully scrolled to the bottom - Prevent potential "Comparison method violates its general contract!" crash -- Fix staggered grid cover being squashed for local source - +- Fix staggered grid cover being squashed for local source (@AwkwardPeak7) + ### Translation - Update translations from Weblate From 33d7c3cd2b11e8440bc5a6d5650db2993fa1edbd Mon Sep 17 00:00:00 2001 From: Hiirbaf <42479509+Hiirbaf@users.noreply.github.com> Date: Tue, 13 May 2025 17:44:48 -0300 Subject: [PATCH 128/181] feat: update user agent (#401) * Switch default user agent to Android Chrome * Emparejamiento con mihon * Update User-Agent --- .../kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt b/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt index 99078e89d9..63a8aa1d1d 100644 --- a/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt +++ b/core/main/src/commonMain/kotlin/eu/kanade/tachiyomi/network/NetworkPreferences.kt @@ -14,6 +14,6 @@ class NetworkPreferences( fun defaultUserAgent() = preferenceStore.getString("default_user_agent", DEFAULT_USER_AGENT) companion object { - const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0" + const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Mobile Safari/537.36" } } From 43a5e8edd830de2dd83b4db09c1cd59f80b92419 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 14 May 2025 04:13:11 +0700 Subject: [PATCH 129/181] fix: Disable MAL's custom user-agent --- .../tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt index f1fa96f48b..412e4a49ed 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt @@ -34,7 +34,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor // Add the authorization header to the original request val authRequest = originalRequest.newBuilder() .addHeader("Authorization", "Bearer ${oauth!!.accessToken}") - .header("User-Agent", "null2264/yokai/${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})") + // .header("User-Agent", "null2264/yokai/${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})") .build() return chain.proceed(authRequest) From 71c26b77fc1f85a72baeadb82e14b2481db74738 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Wed, 14 May 2025 04:37:33 +0700 Subject: [PATCH 130/181] chore: Sync project [skip ci] --- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/issue_report.yml | 2 +- CHANGELOG.md | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index c4388edd69..9e51f1ef03 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -35,7 +35,7 @@ body: required: true - label: If this is an issue with an extension, or a request for an extension, I should be contacting the extensions repository's maintainer/support for help. required: true - - label: I have updated the app to version **[1.9.7.1](https://github.com/null2264/yokai/releases/latest)**. + - label: I have updated the app to version **[1.9.7.2](https://github.com/null2264/yokai/releases/latest)**. required: true - label: I have checked through the app settings for my feature. required: true diff --git a/.github/ISSUE_TEMPLATE/issue_report.yml b/.github/ISSUE_TEMPLATE/issue_report.yml index 5fc99826f9..0b3811d558 100644 --- a/.github/ISSUE_TEMPLATE/issue_report.yml +++ b/.github/ISSUE_TEMPLATE/issue_report.yml @@ -100,7 +100,7 @@ body: required: true - label: I have tried the [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/). required: true - - label: I have updated the app to version **[1.9.7.1](https://github.com/null2264/yokai/releases/latest)**. + - label: I have updated the app to version **[1.9.7.2](https://github.com/null2264/yokai/releases/latest)**. required: true - label: I have updated all installed extensions. required: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f1bb7cf46..0bdc76d109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,12 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - LibraryManga no longer extend MangaImpl - Update dependency androidx.compose:compose-bom to v2025.01.00 - Update dependency gradle to v8.12 +- Update user agent (@Hiirbaf) + +## [1.9.7.2] + +### Fixes +- Fix MyAnimeList timeout issue ## [1.9.7.1] From ea179979b154732731f7e2d49de101a22d585d1c Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Wed, 14 May 2025 05:51:39 +0200 Subject: [PATCH 131/181] chore(i18n): Translations update from Hosted Weblate (#390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/es/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/fi/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/fil/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/fr/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/hi/ Translate-URL: https://hosted.weblate.org/projects/yokai/yokai/zh_Hans/ Translation: Yōkai/Yōkai Co-authored-by: Hiirbaf Co-authored-by: Infy's Tagalog Translations Co-authored-by: MARTINAT Noah Co-authored-by: Ricky Tigg Co-authored-by: Thibault Co-authored-by: UnTamed Fury Co-authored-by: zhongfly <11155705+zhongfly@users.noreply.github.com> --- .../commonMain/moko-resources/es/strings.xml | 24 +++--- .../commonMain/moko-resources/fi/strings.xml | 2 +- .../commonMain/moko-resources/fil/strings.xml | 6 ++ .../commonMain/moko-resources/fr/strings.xml | 74 ++++++++++++++++++- .../commonMain/moko-resources/hi/strings.xml | 5 +- .../moko-resources/zh-rCN/strings.xml | 8 +- 6 files changed, 103 insertions(+), 16 deletions(-) diff --git a/i18n/src/commonMain/moko-resources/es/strings.xml b/i18n/src/commonMain/moko-resources/es/strings.xml index 32dafe0303..480554f8e1 100644 --- a/i18n/src/commonMain/moko-resources/es/strings.xml +++ b/i18n/src/commonMain/moko-resources/es/strings.xml @@ -184,7 +184,7 @@ Añadir Conexión a la red no disponible Descarga detenida - ¿Deseas fijar esta imagen como portada\? + ¿Usar esta imagen como portada? Cerrar Mostrar Cuadrícula compacta @@ -239,8 +239,8 @@ ¡Ya existe una categoría con este nombre! Imagen guardada Opciones - Establecer como cubierta - Cubierta actualizada + Establecer como portada + Portada actualizada Error al actualizar la portada Insignias de descarga Local @@ -435,7 +435,7 @@ Ocultar tolva de categoría Mostrar como Toca el icono de la Biblioteca para mostrar los filtros - Cubiertas uniformes + Portadas uniformes Ocultar el botón de inicio de lectura Cuadrícula agradable Opciones de visualización @@ -473,7 +473,7 @@ Portada guardada Fuente no instalada Recordar selección - Recolocar portada + Restablecer portada Usar predefinida Etiqueta Ordenar y filtrar @@ -616,8 +616,7 @@ Mover páginas dobles Mover únicamente una página Añadido a %1$s - Elimina las portadas en la caché y no utilizadas de las entradas de la biblioteca que se han actualizado. -\nActualmente estás usando: %1$s + Elimina las portadas almacenadas en caché antiguas y sin uso de las entradas de tu biblioteca que se han actualizado. \nActualmente estás usando: %1$s Sin iniciar sesion en %1$s Rosa chicle Florecer de primavera @@ -659,7 +658,7 @@ Igualar fuentes activadas Igualar fuentes marcadas Buscar solo en fuentes fijadas - Eliminar portadas en la caché fuera de la biblioteca + Borrar portadas almacenadas en caché que no están en la biblioteca Iniciando limpieza Limpiar las entradas que no pertenezcan a la biblioteca Eliminar carpetas de capítulos inexistentes, parcialmente descargados o leídos @@ -705,7 +704,7 @@ Ciertos botones se pueden encontrar en otros sitios estando esto desactivado Botones abajo del lector Tamaño de la cuadrícula - Cuadrículas con cubiertas uniformes + Cuadrículas con portadas uniformes También se puede encontrar al expandir los filtros de la biblioteca Expandir / Colapsar todas las categorías Al agrupar la biblioteca por fuentes, estados, etc. @@ -805,7 +804,7 @@ Extensiones actualizadas Instalado %1$s Actualizaciones de la extensión pendientes - Afecta a la rejilla de la biblioteca + Afecta a las portadas de la cuadricula de la biblioteca Restricciones: %1$s Ningún resultado con tus filtros Mostrar categorías vacías al filtrar @@ -817,7 +816,7 @@ Ayudar a traducir Filtrar grupos de scanlations El seguimiento no se puede retirar de %1$s mientras esta fuera de línea - Botones del tema basados en la cubierta + Botones del tema basados en la portada Cerrar sesión ¿Cerrar sesión de %1$s\? Eliminar de %1$s @@ -1014,7 +1013,7 @@ Se requiere WebView para que la aplicación funcione Atrás Adelante - Refrescar + Actualizar Error interno: %s SFW NSFW @@ -1072,4 +1071,5 @@ La huella digital de la clave de firma ya existe No se puede abrir la URL Mover la serie al final + Habilitar la acción de deslizar la fuente diff --git a/i18n/src/commonMain/moko-resources/fi/strings.xml b/i18n/src/commonMain/moko-resources/fi/strings.xml index 381576cfff..e47d743b5c 100644 --- a/i18n/src/commonMain/moko-resources/fi/strings.xml +++ b/i18n/src/commonMain/moko-resources/fi/strings.xml @@ -898,7 +898,7 @@ Lisää tunniste Odotuslista Jäljitintä ei voi poistaa kohteesta %1$s ei-verkkotilassa - Laajamittaiset päivitykset + Yleisesti pätevät päivitykset Päivitykset Kirjasto päivitetty viimeksi: %s Kutista ryhmitetyt luvut diff --git a/i18n/src/commonMain/moko-resources/fil/strings.xml b/i18n/src/commonMain/moko-resources/fil/strings.xml index 892225185f..c3e6c92695 100644 --- a/i18n/src/commonMain/moko-resources/fil/strings.xml +++ b/i18n/src/commonMain/moko-resources/fil/strings.xml @@ -1051,4 +1051,10 @@ Custom na profile sa display Maaaring maayos ang isyu sa mga na-download na kabanata na magkasalungat sa isa\'t isa kapag pareho sila ng pangalan Gumamit ng eksperimental na compose library + Mga naka-save na paghahanap + I-save ang pangalan ng paghahanap + I-save ang kasalukuyang query sa paghahanap? + Di-wastong naka-save na pangalan sa paghahanap + Tanggalin itong naka-save na query sa paghahanap? + Sigurado ka bang gusto mong tanggalin itong naka-save na query sa paghahanap: \'%1$s\'? diff --git a/i18n/src/commonMain/moko-resources/fr/strings.xml b/i18n/src/commonMain/moko-resources/fr/strings.xml index 49f8c33278..3f1c46fb2d 100644 --- a/i18n/src/commonMain/moko-resources/fr/strings.xml +++ b/i18n/src/commonMain/moko-resources/fr/strings.xml @@ -1007,4 +1007,76 @@ Emplacement invalide Vous effectuez une mise à jour à partir d\'une ancienne version et vous ne savez pas quoi sélectionner ? Reportez-vous à la section Mise à niveau Tachiyomi dans le guide de stockage Mihon pour plus d\'information. Pour installer l\'application sur les mises à jour. - \ No newline at end of file + Montrer la queue de téléchargement + Rogner les bords (Bande longue) + Appui long Comportement naviguer + Défaut + Permet l\'installation d\'extensions sans invite utilisateur et active les mises à jour automatiques pour les appareils sous Android 12 + Révoquer toutes les extensions de confiance + Révoquer toutes les extensions de confiance ? + Page %1$d de %2$d + Non sélectionné + Sélectionné + Ouvre une série aléatoire (Global) + Ouvrir le dernier chapitre lu + Appui long Comportement récents + Des extensions malveillantes peuvent lire les identifiants de connexion enregistrés ou exécuter du code arbitraire.\n\nEn faisant confiance à cette extension, vous acceptez ces risques. + Bande longue + Ouvrir les paramètres de découpage hérité + Sur les appareils antérieurs à Android 9.0, vous devez définir les paramètres de découpe manuellement via les paramètres de votre système + Utiliser la bibliothèque de composition expérimentale + Aléatoire + Mises à Jour + Ouvrir le menu extensions / migration + Ouvrir la recherche globale + L\'installateur hérité n\'est pas encore implémenté, retour actuelle à PackageInstaller (par défaut) + Activer l\'action de balayage de chapitre + Par défaut (%d) + Appuyé deux fois pour zoomer + Utilisation du stockage + Hérité + Disponible : %1$s / Total : %2$s + Échec de l\'attribution d\'un dossier persistant. L\'appli peut se comporter de manière inattendu. + Dernière sauvegarde automatique : %s + Inclure les paramètre sensibles (ex. Tokens de connection au tracker) + Activer les actions de balayage pour les sources + Ajouter un répertoire + Remplacer + Empreinte de connection déjà existante + Déplacé la série vers le bas + Appuyé ici pour avoir de l\'aide avec Cloudflare + WebView est requis pour que l\'appli fonctionne + Retour + Avancer + Rafraîchir + Recherche sauvegardé + Sauvegardé l\'objet de recherche? + Sauvegardé le nom de la recherche + Nom de sauvegarde invalide + Supprimer cet objet de recherche sauvegardé ? + Partagé la couverture + Paramètre de l\'app + Paramètre des sources + Doki + Voir le contenu de la zone découpé + Créé un dossier selon le titre du manga + Sauvegardé les pages dans un dossiers à part + Profil d\'affichage personnalisé + Donnée et stockage + Dossier de stockage + Répertoires d\'extension + Ajouter un nouveau répertoire + URL du répertoire invalide + Supprimer le répertoire ? + Êtes-vous sûr de vouloir supprimer ce répertoire \"%s\"? + Répertoire déjà existant + Vous n\'avez pas encore ajouté de répertorié + Répertoire open source + Impossible d\'ouvrir l\'url + Ajouter automatiquement l\'ID + Peut corrigé des erreur de téléchargement avec des chapitre en conflit avec d\'autre ayant le même nom + Appliqué + Juste à l\'instant + Mode de débogage + Scanner les stockage externe pour des entrées + diff --git a/i18n/src/commonMain/moko-resources/hi/strings.xml b/i18n/src/commonMain/moko-resources/hi/strings.xml index ea20c9328c..356e81cd23 100644 --- a/i18n/src/commonMain/moko-resources/hi/strings.xml +++ b/i18n/src/commonMain/moko-resources/hi/strings.xml @@ -581,4 +581,7 @@ लाइब्रेरी के आइटम डीबग जानकारी पृष्ठभूमि गतिविधि - \ No newline at end of file + अधिक विकल्प + आइए कुछ डिफ़ॉल्ट चुनें। आप उन्हें बाद में सेटिंग में जाकर कभी भी बदल सकते हैं। + शुरू हो जाओ + diff --git a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml index 96d4879a3c..0ea27e89bf 100644 --- a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml @@ -1051,4 +1051,10 @@ 重启应用 乱序 使用实验性的书架UI(基于Compose框架) - \ No newline at end of file + 是否确定要删除此项已保存的筛选:“%1$s”? + 已保存的筛选 + 保存名称不可用 + 保存当前的筛选设置? + 保存名称 + 删除此项已保存的筛选设置? + From 1c73b925b1d119ce746c4343d3cee555f2d28bb8 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 23 May 2025 08:07:50 +0700 Subject: [PATCH 132/181] chore(github): Everyone gets to vote [skip ci] --- .github/ISSUE_TEMPLATE/feature_request.yml | 15 +++++++++++++++ .github/ISSUE_TEMPLATE/issue_report.yml | 15 +++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 11 +++++++++++ 3 files changed, 41 insertions(+) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9e51f1ef03..bae9c3625b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -41,3 +41,18 @@ body: required: true - label: I will fill out all of the requested information in this form. required: true + + - type: "textarea" + id: "prioritisation" + attributes: + label: "Is this issue important to you?" + description: | + **Please do not modify this text area!** + + This template let users to vote with a :+1: reaction if they find it important. + This is not a guarantee that highly-requested issues will be fixed first, but it helps us to figure out what's important to users. Please react on other users' issues if you find them important. + value: | + Add a :+1: [reaction] to [issues you find important]. + + [reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/ + [issues you find important]: https://github.com/null2264/yokai/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc diff --git a/.github/ISSUE_TEMPLATE/issue_report.yml b/.github/ISSUE_TEMPLATE/issue_report.yml index 0b3811d558..3c3b5d4590 100644 --- a/.github/ISSUE_TEMPLATE/issue_report.yml +++ b/.github/ISSUE_TEMPLATE/issue_report.yml @@ -106,3 +106,18 @@ body: required: true - label: I have filled out all of the requested information in this form. required: true + + - type: "textarea" + id: "prioritisation" + attributes: + label: "Is this issue important to you?" + description: | + **Please do not modify this text area!** + + This template let users to vote with a :+1: reaction if they find it important. + This is not a guarantee that highly-requested issues will be fixed first, but it helps us to figure out what's important to users. Please react on other users' issues if you find them important. + value: | + Add a :+1: [reaction] to [issues you find important]. + + [reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/ + [issues you find important]: https://github.com/null2264/yokai/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..f771cb985a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,11 @@ + + + +--- + +Add a :+1: [reaction] to [pull requests you find important]. + +[reaction]: https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/ +[pull requests you find important]: https://github.com/null2264/yokai/pulls?q=is%3Aopen+sort%3Areactions-%2B1-desc From 93f819c236c0c8bfd7a8932f4869bbb31ae96f7d Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 26 May 2025 07:37:00 +0700 Subject: [PATCH 133/181] docs(README): Mirror the project to my personal git hosting [skip ci] Just in case --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2dc5958284..ee8f08861a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ A free and open source manga reader [![Discord: Mihon](https://img.shields.io/discord/1195734228319617024.svg?label=&labelColor=6A7EC2&color=7389D8&logo=discord&logoColor=FFFFFF)](https://discord.gg/mihon) [![Mirror: GitLab](https://img.shields.io/badge/mirror-GitLab-orange.svg?labelColor=27303D)](https://gitlab.com/null2264/yokai) +[![Mirror: git.aap](https://img.shields.io/badge/mirror-git.aap-orange.svg?labelColor=D40000)](https://git.aap.my.id/null2264/yokai) [![CI](https://github.com/null2264/yokai/actions/workflows/build_push.yml/badge.svg?labelColor=27303D)](https://github.com/null2264/yokai/actions/workflows/build_push.yml) [![License: Apache-2.0](https://img.shields.io/github/license/null2264/yokai?labelColor=27303D&color=0877d2)](/LICENSE) From 524c00fd4472302900a40986b11a499bb8d81042 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 26 May 2025 07:37:00 +0700 Subject: [PATCH 134/181] docs(README): Mirror the project to my personal git hosting [skip ci] Just in case --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2dc5958284..d4ba494c0d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ A free and open source manga reader [![Discord: Mihon](https://img.shields.io/discord/1195734228319617024.svg?label=&labelColor=6A7EC2&color=7389D8&logo=discord&logoColor=FFFFFF)](https://discord.gg/mihon) [![Mirror: GitLab](https://img.shields.io/badge/mirror-GitLab-orange.svg?labelColor=27303D)](https://gitlab.com/null2264/yokai) +[![Mirror: git.aap](https://img.shields.io/badge/mirror-git.aap-red.svg?labelColor=27303D)](https://git.aap.my.id/null2264/yokai) [![CI](https://github.com/null2264/yokai/actions/workflows/build_push.yml/badge.svg?labelColor=27303D)](https://github.com/null2264/yokai/actions/workflows/build_push.yml) [![License: Apache-2.0](https://img.shields.io/github/license/null2264/yokai?labelColor=27303D&color=0877d2)](/LICENSE) From c7d2ff097069bc53dd47bdec4f0d42c7382336d7 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 26 May 2025 08:00:32 +0700 Subject: [PATCH 135/181] chore: Ignore weblate config [skip ci] --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 094f3c64de..fa0048f78b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ */*/build .kotlin/ kls_database.db +weblate.conf From 850151720badcd38cbc1c17741957c454f70e1f6 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 27 May 2025 21:47:41 +0700 Subject: [PATCH 136/181] feat: Mark duplicate read chapters as read This also refactor how chapters progress are saved. Chapters' progress now save when user "flipped" the page. Closes GH-409 Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- CHANGELOG.md | 2 + .../tachiyomi/ui/reader/ReaderActivity.kt | 20 +-- .../tachiyomi/ui/reader/ReaderViewModel.kt | 139 ++++++++++-------- .../controllers/SettingsLibraryController.kt | 17 ++- .../util/chapter/ChapterSourceSync.kt | 26 +++- .../domain/library/LibraryPreferences.kt | 7 + .../moko-resources/base/strings.xml | 4 + 7 files changed, 134 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bdc76d109..105ff36b02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,12 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Add random library sort - Add the ability to save search queries - Add toggle to enable/disable hide source on swipe (@Hiirbaf) +- Add the ability to mark duplicate read chapters as read (@AntsyLich) ### Changes - Temporarily disable log file - Categories' header now show filtered count when you search the library when you have "Show number of items" enabled (@LeeSF03) +- Chapter progress now saved everything the page is changed ### Fixes - Allow users to bypass onboarding's permission step if Shizuku is installed diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index da7f99e2b8..c0c9694147 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -148,6 +148,14 @@ import eu.kanade.tachiyomi.util.view.setMessage import eu.kanade.tachiyomi.util.view.snack import eu.kanade.tachiyomi.widget.doOnEnd import eu.kanade.tachiyomi.widget.doOnStart +import java.io.ByteArrayOutputStream +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import java.util.Collections +import java.util.Locale +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.roundToInt import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -167,13 +175,6 @@ import yokai.domain.ui.settings.ReaderPreferences import yokai.domain.ui.settings.ReaderPreferences.LandscapeCutoutBehaviour import yokai.i18n.MR import yokai.util.lang.getString -import java.io.ByteArrayOutputStream -import java.text.DecimalFormat -import java.text.DecimalFormatSymbols -import java.util.* -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.roundToInt import android.R as AR /** @@ -512,7 +513,6 @@ class ReaderActivity : BaseActivity() { } } } - viewModel.onSaveInstanceState() super.onSaveInstanceState(outState) } @@ -1304,13 +1304,13 @@ class ReaderActivity : BaseActivity() { } override fun onPause() { - viewModel.saveCurrentChapterReadingProgress() + viewModel.flushReadTimer() super.onPause() } override fun onResume() { super.onResume() - viewModel.setReadStartTime() + viewModel.restartReadTimer() } fun reloadChapters(doublePages: Boolean, force: Boolean = false) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index d2e6e31b04..960654e072 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -55,9 +55,7 @@ import eu.kanade.tachiyomi.util.system.withIOContext import eu.kanade.tachiyomi.util.system.withUIContext import java.util.Date import java.util.concurrent.CancellationException -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -81,6 +79,7 @@ import yokai.domain.chapter.models.ChapterUpdate import yokai.domain.download.DownloadPreferences import yokai.domain.history.interactor.GetHistory import yokai.domain.history.interactor.UpsertHistory +import yokai.domain.library.LibraryPreferences import yokai.domain.manga.interactor.GetManga import yokai.domain.manga.interactor.InsertManga import yokai.domain.manga.interactor.UpdateManga @@ -102,6 +101,7 @@ class ReaderViewModel( private val chapterFilter: ChapterFilter = Injekt.get(), private val storageManager: StorageManager = Injekt.get(), private val downloadPreferences: DownloadPreferences = Injekt.get(), + private val libraryPreferences: LibraryPreferences = Injekt.get(), ) : ViewModel() { private val getCategories: GetCategories by injectLazy() private val getChapter: GetChapter by injectLazy() @@ -160,8 +160,6 @@ class ReaderViewModel( private var chapterItems = emptyList() - private var scope = CoroutineScope(Job() + Dispatchers.Default) - private var hasTrackers: Boolean = false private suspend fun checkTrackers(manga: Manga) = getTrack.awaitAllByMangaId(manga.id).isNotEmpty() @@ -192,24 +190,12 @@ class ReaderViewModel( val currentChapters = state.value.viewerChapters if (currentChapters != null) { currentChapters.unref() - saveReadingProgress(currentChapters.currChapter) chapterToDownload?.let { downloadManager.addDownloadsToStartOfQueue(listOf(it)) } } } - /** - * Called when the activity is saved and not changing configurations. It updates the database - * to persist the current progress of the active chapter. - */ - fun onSaveInstanceState() { - val currentChapter = getCurrentChapter() ?: return - viewModelScope.launchNonCancellableIO { - saveChapterProgress(currentChapter) - } - } - /** * Whether this presenter is initialized yet. */ @@ -375,12 +361,15 @@ class ReaderViewModel( * Called when the user changed to the given [chapter] when changing pages from the viewer. * It's used only to set this chapter as active. */ - private suspend fun loadNewChapter(chapter: ReaderChapter) { + private fun loadNewChapter(chapter: ReaderChapter) { val loader = loader ?: return - Logger.d { "Loading ${chapter.chapter.url}" } + viewModelScope.launchIO { + Logger.d { "Loading ${chapter.chapter.url}" } + + flushReadTimer() + restartReadTimer() - withIOContext { try { loadChapter(loader, chapter) } catch (e: Throwable) { @@ -511,28 +500,15 @@ class ReaderViewModel( val selectedChapter = page.chapter // Save last page read and mark as read if needed - selectedChapter.chapter.last_page_read = page.index - selectedChapter.chapter.pages_left = - (selectedChapter.pages?.size ?: page.index) - page.index - val shouldTrack = !preferences.incognitoMode().get() || hasTrackers - if (shouldTrack && - // For double pages, check if the second to last page is doubled up - ( - (selectedChapter.pages?.lastIndex == page.index && page.firstHalf != true) || - (hasExtraPage && selectedChapter.pages?.lastIndex?.minus(1) == page.index) - ) - ) { - selectedChapter.chapter.read = true - updateTrackChapterAfterReading(selectedChapter) - deleteChapterIfNeeded(selectedChapter) + viewModelScope.launchNonCancellableIO { + saveChapterProgress(selectedChapter, page, hasExtraPage) } if (selectedChapter != currentChapters.currChapter) { Logger.d { "Setting ${selectedChapter.chapter.url} as active" } - saveReadingProgress(currentChapters.currChapter) - setReadStartTime() - scope.launch { loadNewChapter(selectedChapter) } + loadNewChapter(selectedChapter) } + val pages = page.chapter.pages ?: return val inDownloadRange = page.number.toDouble() / pages.size > 0.2 if (inDownloadRange) { @@ -620,28 +596,28 @@ class ReaderViewModel( } } - /** - * Called when reader chapter is changed in reader or when activity is paused. - */ - private fun saveReadingProgress(readerChapter: ReaderChapter) { - viewModelScope.launchNonCancellableIO { - saveChapterProgress(readerChapter) - saveChapterHistory(readerChapter) - } - } - - fun saveCurrentChapterReadingProgress() = getCurrentChapter()?.let { saveReadingProgress(it) } - /** * Saves this [readerChapter]'s progress (last read page and whether it's read). * If incognito mode isn't on or has at least 1 tracker */ - private suspend fun saveChapterProgress(readerChapter: ReaderChapter) { + private suspend fun saveChapterProgress(readerChapter: ReaderChapter, page: ReaderPage, hasExtraPage: Boolean) { readerChapter.requestedPage = readerChapter.chapter.last_page_read getChapter.awaitById(readerChapter.chapter.id!!)?.let { dbChapter -> readerChapter.chapter.bookmark = dbChapter.bookmark } - if (!preferences.incognitoMode().get() || hasTrackers) { + + val shouldTrack = !preferences.incognitoMode().get() || hasTrackers + if (shouldTrack && page.status != Page.State.ERROR) { + readerChapter.chapter.last_page_read = page.index + readerChapter.chapter.pages_left = (readerChapter.pages?.size ?: page.index) - page.index + // For double pages, check if the second to last page is doubled up + if ( + (readerChapter.pages?.lastIndex == page.index && page.firstHalf != true) || + (hasExtraPage && readerChapter.pages?.lastIndex?.minus(1) == page.index) + ) { + onChapterReadComplete(readerChapter) + } + updateChapter.await( ChapterUpdate( id = readerChapter.chapter.id!!, @@ -654,24 +630,57 @@ class ReaderViewModel( } } + private suspend fun onChapterReadComplete(readerChapter: ReaderChapter) { + readerChapter.chapter.read = true + updateTrackChapterAfterReading(readerChapter) + deleteChapterIfNeeded(readerChapter) + + val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get() + .contains(LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_EXISTING) + if (!markDuplicateAsRead) return + + val duplicateUnreadChapters = chapterList + .mapNotNull { + val chapter = it.chapter + if ( + !chapter.read && + chapter.isRecognizedNumber && + chapter.chapter_number == readerChapter.chapter.chapter_number + ) { + ChapterUpdate(id = chapter.id!!, read = true) + } else { + null + } + } + updateChapter.awaitAll(duplicateUnreadChapters) + } + + fun restartReadTimer() { + chapterReadStartTime = Date().time + } + + fun flushReadTimer() { + getCurrentChapter()?.let { + viewModelScope.launchNonCancellableIO { + saveChapterHistory(it) + } + } + } + /** * Saves this [readerChapter] last read history. */ private suspend fun saveChapterHistory(readerChapter: ReaderChapter) { - if (!preferences.incognitoMode().get()) { - val readAt = Date().time - val sessionReadDuration = chapterReadStartTime?.let { readAt - it } ?: 0 - val history = History.create(readerChapter.chapter).apply { - last_read = readAt - time_read = sessionReadDuration - } - upsertHistory.await(history) - chapterReadStartTime = null - } - } + if (preferences.incognitoMode().get()) return - fun setReadStartTime() { - chapterReadStartTime = Date().time + val endTime = Date().time + val sessionReadDuration = chapterReadStartTime?.let { endTime - it } ?: 0 + val history = History.create(readerChapter.chapter).apply { + last_read = endTime + time_read = sessionReadDuration + } + upsertHistory.await(history) + chapterReadStartTime = null } /** @@ -876,7 +885,7 @@ class ReaderViewModel( } fun saveImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) { - scope.launch { + viewModelScope.launch { if (firstPage.status != Page.State.READY) return@launch if (secondPage.status != Page.State.READY) return@launch val manga = manga ?: return@launch @@ -925,7 +934,7 @@ class ReaderViewModel( } fun shareImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) { - scope.launch { + viewModelScope.launch { if (firstPage.status != Page.State.READY) return@launch if (secondPage.status != Page.State.READY) return@launch val manga = manga ?: return@launch diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt index d7d0977e84..e8068a9cb9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt @@ -34,6 +34,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import yokai.domain.category.interactor.GetCategories +import yokai.domain.library.LibraryPreferences import yokai.domain.manga.interactor.GetLibraryManga import yokai.domain.ui.UiPreferences import yokai.i18n.MR @@ -48,6 +49,7 @@ class SettingsLibraryController : SettingsLegacyController() { private val getCategories: GetCategories by injectLazy() private val uiPreferences: UiPreferences by injectLazy() + private val libraryPreferences: LibraryPreferences by injectLazy() override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { titleRes = MR.strings.library @@ -212,12 +214,25 @@ class SettingsLibraryController : SettingsLegacyController() { } preferenceCategory { - titleRes = MR.strings.chapters + titleRes = MR.strings.pref_behavior switchPreference { bindTo(uiPreferences.enableChapterSwipeAction()) titleRes = MR.strings.enable_chapter_swipe_action } + multiSelectListPreferenceMat(activity) { + bindTo(preferences.libraryUpdateMangaRestriction()) + titleRes = MR.strings.pref_mark_as_read_duplicate_read_chapter + val entries = mapOf( + MR.strings.pref_mark_as_read_duplicate_read_chapter_existing to + LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_EXISTING, + MR.strings.pref_mark_as_read_duplicate_read_chapter_new to + LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_NEW, + ) + entriesRes = entries.keys.toTypedArray() + entryValues = entries.values.toList() + noSelectionRes = MR.strings.none + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt index 987d76c91b..54211507c5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterSourceSync.kt @@ -17,6 +17,7 @@ import yokai.domain.chapter.interactor.InsertChapter import yokai.domain.chapter.interactor.UpdateChapter import yokai.domain.chapter.models.ChapterUpdate import yokai.domain.chapter.services.ChapterRecognition +import yokai.domain.library.LibraryPreferences import yokai.domain.manga.interactor.UpdateManga import yokai.domain.manga.models.MangaUpdate @@ -39,6 +40,7 @@ suspend fun syncChaptersWithSource( updateChapter: UpdateChapter = Injekt.get(), updateManga: UpdateManga = Injekt.get(), handler: DatabaseHandler = Injekt.get(), + libraryPreferences: LibraryPreferences = Injekt.get(), ): Pair, List> { if (rawSourceChapters.isEmpty()) { throw Exception("No chapters found") @@ -122,11 +124,18 @@ suspend fun syncChaptersWithSource( return Pair(emptyList(), emptyList()) } - val reAdded = mutableListOf() + val changedOrDuplicateReadUrls = mutableSetOf() val deletedChapterNumbers = TreeSet() val deletedReadChapterNumbers = TreeSet() val deletedBookmarkedChapterNumbers = TreeSet() + + val readChapterNumbers = dbChapters + .asSequence() + .filter { it.read && it.isRecognizedNumber } + .map { it.chapter_number } + .toSet() + toDelete.forEach { if (it.read) deletedReadChapterNumbers.add(it.chapter_number) if (it.bookmark) deletedBookmarkedChapterNumbers.add(it.chapter_number) @@ -135,6 +144,9 @@ suspend fun syncChaptersWithSource( val now = Date().time + val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get() + .contains(LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_NEW) + // Date fetch is set in such a way that the upper ones will have bigger value than the lower ones // Sources MUST return the chapters from most to less recent, which is common. var itemCount = toAdd.size @@ -143,6 +155,11 @@ suspend fun syncChaptersWithSource( chapter.date_fetch = now + itemCount-- + if (chapter.chapter_number in readChapterNumbers && markDuplicateAsRead) { + changedOrDuplicateReadUrls.add(chapter.url) + chapter.read = true + } + if (!chapter.isRecognizedNumber || chapter.chapter_number !in deletedChapterNumbers) return@map chapter chapter.read = chapter.chapter_number in deletedReadChapterNumbers @@ -154,7 +171,7 @@ suspend fun syncChaptersWithSource( chapter.date_fetch = it.date_fetch } - reAdded.add(chapter) + changedOrDuplicateReadUrls.add(chapter.url) chapter } @@ -192,14 +209,13 @@ suspend fun syncChaptersWithSource( manga.last_update = Date().time updateManga.await(MangaUpdate(manga.id!!, lastUpdate = manga.last_update)) - val reAddedUrls = reAdded.map { it.url }.toHashSet() val filteredScanlators = ChapterUtil.getScanlators(manga.filtered_scanlators).toHashSet() return Pair( updatedToAdd.filterNot { - it.url in reAddedUrls || it.scanlator in filteredScanlators + it.url in changedOrDuplicateReadUrls || it.scanlator in filteredScanlators }, - toDelete.filterNot { it.url in reAddedUrls }, + toDelete.filterNot { it.url in changedOrDuplicateReadUrls }, ) } diff --git a/app/src/main/java/yokai/domain/library/LibraryPreferences.kt b/app/src/main/java/yokai/domain/library/LibraryPreferences.kt index e2dc5342f3..ab1094de0f 100644 --- a/app/src/main/java/yokai/domain/library/LibraryPreferences.kt +++ b/app/src/main/java/yokai/domain/library/LibraryPreferences.kt @@ -4,4 +4,11 @@ import eu.kanade.tachiyomi.core.preference.PreferenceStore class LibraryPreferences(private val preferenceStore: PreferenceStore) { fun randomSortSeed() = preferenceStore.getInt("library_random_sort_seed", 0) + + fun markDuplicateReadChapterAsRead() = preferenceStore.getStringSet("mark_duplicate_read_chapter_read", emptySet()) + + companion object { + const val MARK_DUPLICATE_READ_CHAPTER_READ_NEW = "new" + const val MARK_DUPLICATE_READ_CHAPTER_READ_EXISTING = "existing" + } } diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index ef4ade4837..3e52a4026a 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -167,6 +167,10 @@ Ungrouped Use experimental compose library + Behavior + Mark duplicate read chapter as read + After reading a chapter + After fetching new chapter Sort by From 7964ac87c669bf37d1db7159d363e980be0d29a7 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Tue, 27 May 2025 22:25:19 +0700 Subject: [PATCH 137/181] fix: Wrong pref to bound I should really stop coding in the middle of the night --- .../ui/setting/controllers/SettingsLibraryController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt index e8068a9cb9..77ad3199ba 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsLibraryController.kt @@ -221,7 +221,7 @@ class SettingsLibraryController : SettingsLegacyController() { titleRes = MR.strings.enable_chapter_swipe_action } multiSelectListPreferenceMat(activity) { - bindTo(preferences.libraryUpdateMangaRestriction()) + bindTo(libraryPreferences.markDuplicateReadChapterAsRead()) titleRes = MR.strings.pref_mark_as_read_duplicate_read_chapter val entries = mapOf( MR.strings.pref_mark_as_read_duplicate_read_chapter_existing to From 18528fbd92f1f952a81b5c9db60d633e7296a453 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+antsylich@users.noreply.github.com> Date: Wed, 28 May 2025 09:05:14 +0700 Subject: [PATCH 138/181] fix: Fix mark existing duplicate read chapters as read option not working in some cases --- .../eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index 960654e072..f3699a62cf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -67,6 +67,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -156,6 +157,11 @@ class ReaderViewModel( private var finished = false private var chapterToDownload: Download? = null + private val unfilteredChapterList by lazy { + val manga = manga!! + runBlocking { getChapter.awaitAll(manga, filterScanlators = false) } + } + private lateinit var chapterList: List private var chapterItems = emptyList() @@ -639,9 +645,8 @@ class ReaderViewModel( .contains(LibraryPreferences.MARK_DUPLICATE_READ_CHAPTER_READ_EXISTING) if (!markDuplicateAsRead) return - val duplicateUnreadChapters = chapterList - .mapNotNull { - val chapter = it.chapter + val duplicateUnreadChapters = unfilteredChapterList + .mapNotNull { chapter -> if ( !chapter.read && chapter.isRecognizedNumber && From 370bb62ef93a7ce4a5a590f10804f852fb382621 Mon Sep 17 00:00:00 2001 From: AwkwardPeak7 <48650614+awkwardpeak7@users.noreply.github.com> Date: Wed, 28 May 2025 09:19:42 +0700 Subject: [PATCH 139/181] refactor: Change Page.State to sealed interface --- .../data/download/DownloadManager.kt | 4 +-- .../tachiyomi/data/download/Downloader.kt | 13 ++++---- .../tachiyomi/data/download/model/Download.kt | 2 +- .../tachiyomi/ui/reader/ReaderActivity.kt | 2 +- .../tachiyomi/ui/reader/ReaderViewModel.kt | 16 +++++----- .../ui/reader/loader/ArchivePageLoader.kt | 2 +- .../ui/reader/loader/DirectoryPageLoader.kt | 2 +- .../ui/reader/loader/DownloadPageLoader.kt | 2 +- .../ui/reader/loader/EpubPageLoader.kt | 2 +- .../ui/reader/loader/HttpPageLoader.kt | 28 ++++++++--------- .../tachiyomi/ui/reader/model/InsertPage.kt | 2 +- .../ui/reader/viewer/pager/PagerPageHolder.kt | 30 +++++++++---------- .../viewer/webtoon/WebtoonPageHolder.kt | 10 +++---- .../eu/kanade/tachiyomi/source/model/Page.kt | 14 ++++----- 14 files changed, 63 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index 830c9b071e..8371e1c3b9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -21,8 +21,8 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import yokai.domain.download.DownloadPreferences import yokai.i18n.MR import yokai.util.lang.getString @@ -171,7 +171,7 @@ class DownloadManager( return files.sortedBy { it.name } .mapIndexed { i, file -> - Page(i, uri = file.uri).apply { status = Page.State.READY } + Page(i, uri = file.uri).apply { status = Page.State.Ready } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 2739920559..23ac9773b7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.download import android.content.Context -import android.os.Looper import co.touchlab.kermit.Logger import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.data.cache.ChapterCache @@ -55,8 +54,8 @@ import kotlinx.coroutines.supervisorScope import nl.adaptivity.xmlutil.serialization.XML import okhttp3.Response import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy import yokai.core.archive.ZipWriter import yokai.core.metadata.COMIC_INFO_FILE import yokai.core.metadata.ComicInfo @@ -365,11 +364,11 @@ class Downloader( // Get all the URLs to the source images, fetch pages if necessary pageList.filter { it.imageUrl.isNullOrEmpty() }.forEach { page -> - page.status = Page.State.LOAD_PAGE + page.status = Page.State.LoadPage try { page.imageUrl = download.source.getImageUrl(page) } catch (e: Throwable) { - page.status = Page.State.ERROR + page.status = Page.State.Error } } @@ -494,12 +493,12 @@ class Downloader( page.uri = file.uri page.progress = 100 - page.status = Page.State.READY + page.status = Page.State.Ready } catch (e: Throwable) { if (e is CancellationException) throw e // Mark this page as error and allow to download the remaining page.progress = 0 - page.status = Page.State.ERROR + page.status = Page.State.Error notifier.onError(e.message, chapName, download.manga.title) } } @@ -518,7 +517,7 @@ class Downloader( tmpDir: UniFile, filename: String, ): UniFile { - page.status = Page.State.DOWNLOAD_IMAGE + page.status = Page.State.DownloadImage page.progress = 0 return flow { val response = source.getImage(page) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt index de3264b16c..4386bfd43b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/model/Download.kt @@ -22,7 +22,7 @@ class Download(val source: HttpSource, val manga: Manga, val chapter: Chapter) { get() = pages?.sumOf(Page::progress) ?: 0 val downloadedImages: Int - get() = pages?.count { it.status == Page.State.READY } ?: 0 + get() = pages?.count { it.status is Page.State.Ready } ?: 0 @Transient private val _statusFlow = MutableStateFlow(State.NOT_DOWNLOADED) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index c0c9694147..eb45718063 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -1655,7 +1655,7 @@ class ReaderActivity : BaseActivity() { } private fun showSetCoverPrompt(page: ReaderPage) { - if (page.status != Page.State.READY) return + if (page.status !is Page.State.Ready) return materialAlertDialog() .setMessage(MR.strings.use_image_as_cover) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index f3699a62cf..5097eb1809 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -613,7 +613,7 @@ class ReaderViewModel( } val shouldTrack = !preferences.incognitoMode().get() || hasTrackers - if (shouldTrack && page.status != Page.State.ERROR) { + if (shouldTrack && page.status !is Page.State.Error) { readerChapter.chapter.last_page_read = page.index readerChapter.chapter.pages_left = (readerChapter.pages?.size ?: page.index) - page.index // For double pages, check if the second to last page is doubled up @@ -860,7 +860,7 @@ class ReaderViewModel( * There's also a notification to allow sharing the image somewhere else or deleting it. */ fun saveImage(page: ReaderPage) { - if (page.status != Page.State.READY) return + if (page.status !is Page.State.Ready) return val manga = manga ?: return val context = Injekt.get() @@ -891,8 +891,8 @@ class ReaderViewModel( fun saveImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) { viewModelScope.launch { - if (firstPage.status != Page.State.READY) return@launch - if (secondPage.status != Page.State.READY) return@launch + if (firstPage.status !is Page.State.Ready) return@launch + if (secondPage.status !is Page.State.Ready) return@launch val manga = manga ?: return@launch val context = Injekt.get() @@ -926,7 +926,7 @@ class ReaderViewModel( * image will be kept so it won't be taking lots of internal disk space. */ fun shareImage(page: ReaderPage) { - if (page.status != Page.State.READY) return + if (page.status !is Page.State.Ready) return val manga = manga ?: return val context = Injekt.get() @@ -940,8 +940,8 @@ class ReaderViewModel( fun shareImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) { viewModelScope.launch { - if (firstPage.status != Page.State.READY) return@launch - if (secondPage.status != Page.State.READY) return@launch + if (firstPage.status !is Page.State.Ready) return@launch + if (secondPage.status !is Page.State.Ready) return@launch val manga = manga ?: return@launch val context = Injekt.get() @@ -958,7 +958,7 @@ class ReaderViewModel( * Sets the image of this [page] as cover and notifies the UI of the result. */ fun setAsCover(page: ReaderPage) { - if (page.status != Page.State.READY) return + if (page.status !is Page.State.Ready) return val manga = manga ?: return val stream = page.stream ?: return diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt index dfa63cbba2..16c5a371fe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt @@ -31,7 +31,7 @@ internal class ArchivePageLoader(private val reader: ArchiveReader) : PageLoader .mapIndexed { i, entry -> ReaderPage(i).apply { stream = { reader.getInputStream(entry.name)!! } - status = Page.State.READY + status = Page.State.Ready } } .toList() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt index ba4a10bd6e..825b14a9dd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DirectoryPageLoader.kt @@ -24,7 +24,7 @@ class DirectoryPageLoader(val file: UniFile) : PageLoader() { val streamFn = { file.openInputStream() } ReaderPage(i).apply { stream = streamFn - status = Page.State.READY + status = Page.State.Ready } } ?: emptyList() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt index 254db17580..b6efbb1fcf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt @@ -60,7 +60,7 @@ class DownloadPageLoader( ReaderPage(page.index, page.url, page.imageUrl, stream = { context.contentResolver.openInputStream(page.uri ?: Uri.EMPTY)!! },).apply { - status = Page.State.READY + status = Page.State.Ready } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt index 77c782e87e..c5a713fa83 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt @@ -28,7 +28,7 @@ class EpubPageLoader(private val epub: EpubReader) : PageLoader() { val streamFn = { epub.getInputStream(path)!! } ReaderPage(i).apply { stream = streamFn - status = Page.State.READY + status = Page.State.Ready } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt index ce32b60d34..4c91b5ea8f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt @@ -51,7 +51,7 @@ class HttpPageLoader( emit(runInterruptible { queue.take() }.page) } } - .filter { it.status == Page.State.QUEUE } + .filter { it.status is Page.State.Queue } .collect { _loadPage(it) } @@ -108,17 +108,17 @@ class HttpPageLoader( val imageUrl = page.imageUrl // Check if the image has been deleted - if (page.status == Page.State.READY && imageUrl != null && !chapterCache.isImageInCache(imageUrl)) { - page.status = Page.State.QUEUE + if (page.status is Page.State.Ready && imageUrl != null && !chapterCache.isImageInCache(imageUrl)) { + page.status = Page.State.Queue } // Automatically retry failed pages when subscribed to this page - if (page.status == Page.State.ERROR) { - page.status = Page.State.QUEUE + if (page.status is Page.State.Error) { + page.status = Page.State.Queue } val queuedPages = mutableListOf() - if (page.status == Page.State.QUEUE) { + if (page.status is Page.State.Queue) { queuedPages += PriorityPage(page, 1).also { queue.offer(it) } } queuedPages += preloadNextPages(page, preloadSize) @@ -126,7 +126,7 @@ class HttpPageLoader( suspendCancellableCoroutine { continuation -> continuation.invokeOnCancellation { queuedPages.forEach { - if (it.page.status == Page.State.QUEUE) { + if (it.page.status is Page.State.Queue) { queue.remove(it) } } @@ -146,7 +146,7 @@ class HttpPageLoader( return pages .subList(pageIndex + 1, min(pageIndex + 1 + amount, pages.size)) .mapNotNull { - if (it.status == Page.State.QUEUE) { + if (it.status is Page.State.Queue) { PriorityPage(it, 0).apply { queue.offer(this) } } else { null @@ -158,8 +158,8 @@ class HttpPageLoader( * Retries a page. This method is only called from user interaction on the viewer. */ override fun retryPage(page: ReaderPage) { - if (page.status == Page.State.ERROR) { - page.status = Page.State.QUEUE + if (page.status is Page.State.Error) { + page.status = Page.State.Queue } queue.offer(PriorityPage(page, 2)) } @@ -192,21 +192,21 @@ class HttpPageLoader( private suspend fun _loadPage(page: ReaderPage) { try { if (page.imageUrl.isNullOrEmpty()) { - page.status = Page.State.LOAD_PAGE + page.status = Page.State.LoadPage page.imageUrl = source.getImageUrl(page) } val imageUrl = page.imageUrl!! if (!chapterCache.isImageInCache(imageUrl)) { - page.status = Page.State.DOWNLOAD_IMAGE + page.status = Page.State.DownloadImage val imageResponse = source.getImage(page) chapterCache.putImageToCache(imageUrl, imageResponse) } page.stream = { chapterCache.getImageFile(imageUrl).inputStream() } - page.status = Page.State.READY + page.status = Page.State.Ready } catch (e: Throwable) { - page.status = Page.State.ERROR + page.status = Page.State.Error if (e is CancellationException) { throw e } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt index 254a7d6309..5bb4ec428d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt @@ -12,6 +12,6 @@ class InsertPage(parent: ReaderPage) : ReaderPage( fullPage = true firstHalf = false stream = parent.stream - status = State.READY + status = State.Ready } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index 8dc4dcc000..96d657296a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -109,8 +109,8 @@ class PagerPageHolder( */ private var extraProgressJob: Job? = null - private var status = Page.State.READY - private var extraStatus = Page.State.READY + private var status = Page.State.Ready + private var extraStatus = Page.State.Ready private var progress: Int = 0 private var extraProgress: Int = 0 @@ -337,19 +337,19 @@ class PagerPageHolder( */ private suspend fun processStatus(status: Page.State) { when (status) { - Page.State.QUEUE -> setQueued() - Page.State.LOAD_PAGE -> setLoading() - Page.State.DOWNLOAD_IMAGE -> { + is Page.State.Queue -> setQueued() + is Page.State.LoadPage -> setLoading() + is Page.State.DownloadImage -> { launchProgressJob() setDownloading() } - Page.State.READY -> { - if (extraStatus == Page.State.READY || extraPage == null) { + is Page.State.Ready -> { + if (extraPage == null) { setImage() } cancelProgressJob(1) } - Page.State.ERROR -> { + is Page.State.Error -> { setError() cancelProgressJob(1) } @@ -363,19 +363,17 @@ class PagerPageHolder( */ private suspend fun processStatus2(status: Page.State) { when (status) { - Page.State.QUEUE -> setQueued() - Page.State.LOAD_PAGE -> setLoading() - Page.State.DOWNLOAD_IMAGE -> { + is Page.State.Queue -> setQueued() + is Page.State.LoadPage -> setLoading() + is Page.State.DownloadImage -> { launchProgressJob2() setDownloading() } - Page.State.READY -> { - if (this.status == Page.State.READY) { - setImage() - } + is Page.State.Ready -> { + setImage() cancelProgressJob(2) } - Page.State.ERROR -> { + is Page.State.Error -> { setError() cancelProgressJob(2) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt index e1428aa4cc..52c60f7adc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt @@ -128,9 +128,9 @@ class WebtoonPageHolder( launchIO { loader.loadPage(page) } page.statusFlow.collectLatest { status -> when (status) { - Page.State.QUEUE -> setQueued() - Page.State.LOAD_PAGE -> setLoading() - Page.State.DOWNLOAD_IMAGE -> { + is Page.State.Queue -> setQueued() + is Page.State.LoadPage -> setLoading() + is Page.State.DownloadImage -> { setDownloading() scope.launch { page.progressFlow.collectLatest { value -> @@ -138,8 +138,8 @@ class WebtoonPageHolder( } } } - Page.State.READY -> setImage() - Page.State.ERROR -> setError() + is Page.State.Ready -> setImage() + is Page.State.Error -> setError() } } } diff --git a/source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/Page.kt b/source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/Page.kt index 7ce18934f3..ce350c615c 100644 --- a/source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/Page.kt +++ b/source/api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/Page.kt @@ -19,7 +19,7 @@ open class Page( get() = index + 1 @Transient - private val _statusFlow = MutableStateFlow(State.QUEUE) + private val _statusFlow = MutableStateFlow(State.Queue) @Transient val statusFlow = _statusFlow.asStateFlow() @@ -48,11 +48,11 @@ open class Page( } } - enum class State { - QUEUE, - LOAD_PAGE, - DOWNLOAD_IMAGE, - READY, - ERROR, + sealed interface State { + data object Queue : State + data object LoadPage : State + data object DownloadImage : State + data object Ready : State + data object Error : State } } From e22559b2df4bf2a3b6f7fa243ee533d5c9967eaf Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Fri, 30 May 2025 08:33:09 +0700 Subject: [PATCH 140/181] fix(renovate): Ignore more jitpack packages [skip ci] --- .renovaterc.json5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.renovaterc.json5 b/.renovaterc.json5 index c28595b97f..6808816504 100644 --- a/.renovaterc.json5 +++ b/.renovaterc.json5 @@ -24,6 +24,8 @@ 'com.github.tachiyomiorg:image-decoder', 'com.github.tachiyomiorg:unifile', 'com.github.tachiyomiorg:conductor-support-preference', + 'com.github.chrisbanes:PhotoView', + 'com.github.PhilJay:MPAndroidChart', ], enabled: false, }, From 89c5e997cc7335e086d80761279293da5a33c23f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 08:49:47 +0700 Subject: [PATCH 141/181] chore(deps): Update null2264/actions digest to 363cb9c (#411) * chore(deps): Update null2264/actions digest to 363cb9c * fix: Action was renamed --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ahmad Ansori Palembani <46041660+null2264@users.noreply.github.com> --- .github/workflows/build_check.yml | 2 +- .github/workflows/build_push.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_check.yml b/.github/workflows/build_check.yml index fe3797535f..74bb37273e 100644 --- a/.github/workflows/build_check.yml +++ b/.github/workflows/build_check.yml @@ -30,7 +30,7 @@ jobs: ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;35.0.0" - name: Setup Gradle - uses: null2264/actions/gradle-setup@a4d662095a2f2af1ed24f1228eb6e55b0f9f1f29 + uses: null2264/actions/gradle-java-setup@363cb9cf3d66bd9c72ed6860142c6b2c121d7e94 with: java: 17 distro: temurin diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 3c93697182..57f13cf1de 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -43,7 +43,7 @@ jobs: ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;35.0.0" - name: Setup Gradle - uses: null2264/actions/gradle-setup@a4d662095a2f2af1ed24f1228eb6e55b0f9f1f29 + uses: null2264/actions/gradle-java-setup@363cb9cf3d66bd9c72ed6860142c6b2c121d7e94 with: java: 17 distro: temurin @@ -154,7 +154,7 @@ jobs: echo "STAGE=${stage}" >> $GITHUB_OUTPUT - name: Sign APK - uses: null2264/actions/android-signer@a4d662095a2f2af1ed24f1228eb6e55b0f9f1f29 + uses: null2264/actions/android-signer@363cb9cf3d66bd9c72ed6860142c6b2c121d7e94 if: env.VERSION_TAG != '' with: releaseDir: app/build/outputs/apk/standard/${{ steps.version_stage.outputs.STAGE }} From db0af71901a46a9660953b975be418c7f00e1dc6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:07:50 +0700 Subject: [PATCH 142/181] chore(deps): Update plugin firebase-crashlytics to v3.0.3 (#412) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7d3fc9ec4e..284ecf6b09 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -106,7 +106,7 @@ voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", vers [plugins] aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" } -firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "3.0.2" } +firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "3.0.3" } google-services = { id = "com.google.gms.google-services", version = "4.4.2" } gradle-versions = { id = "com.github.ben-manes.versions", version = "0.51.0" } kotlinter = { id = "org.jmailen.kotlinter", version = "5.0.1" } From 75191dde05228a3f600d9ce80139743b975e3027 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:08:04 +0700 Subject: [PATCH 143/181] fix(deps): Update dependency androidx.constraintlayout:constraintlayout to v2.2.1 (#413) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/androidx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index efae61e57b..0e82168f33 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -16,7 +16,7 @@ cardview = { module = "androidx.cardview:cardview", version = "1.0.0" } core = { module = "androidx.core:core-ktx", version = "1.15.0" } core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" } glance-appwidget = { module = "androidx.glance:glance-appwidget", version = "1.1.1" } -layout-constraint = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.0" } +layout-constraint = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.1" } layout-swiperefresh = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" } lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle" } lifecycle-livedata = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle" } From 6a7b38612776a55ef092b3c15ce6d4c616a6f96e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:08:23 +0700 Subject: [PATCH 144/181] fix(deps): Update dependency androidx.work:work-runtime-ktx to v2.10.1 (#414) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/androidx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 0e82168f33..5be34c29c5 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -30,7 +30,7 @@ preference = { module = "androidx.preference:preference-ktx", version = "1.2.1" recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.3.2" } sqlite = { module = "androidx.sqlite:sqlite", version = "2.4.0" } webkit = { module = "androidx.webkit:webkit", version = "1.12.0" } -work = { module = "androidx.work:work-runtime-ktx", version = "2.10.0" } +work = { module = "androidx.work:work-runtime-ktx", version = "2.10.1" } window = { module = "androidx.window:window", version = "1.3.0" } [bundles] From 1655540a16f8e6e00f55bf4b3a6002293033adc6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:08:40 +0700 Subject: [PATCH 145/181] fix(deps): Update dependency com.android.tools:desugar_jdk_libs to v2.1.5 (#415) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 284ecf6b09..05bd1c387e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,7 @@ compose-theme-adapter3 = { module = "com.google.accompanist:accompanist-themeada conductor = { module = "com.bluelinelabs:conductor", version = "4.0.0-preview-4" } conductor-support-preference = { module = "com.github.tachiyomiorg:conductor-support-preference", version = "3.0.0" } conscrypt = { module = "org.conscrypt:conscrypt-android", version = "2.5.2" } -desugar = { module = "com.android.tools:desugar_jdk_libs", version = "2.1.3" } +desugar = { module = "com.android.tools:desugar_jdk_libs", version = "2.1.5" } directionalviewpager = { module = "com.github.tachiyomiorg:DirectionalViewPager", version = "1.0.0" } disklrucache = { module = "com.jakewharton:disklrucache", version = "2.0.2" } fastadapter-extensions-binding = { module = "com.mikepenz:fastadapter-extensions-binding", version.ref = "fast_adapter" } From 903a37e3902229f18ab6d03e7c6497ee3c8f8636 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:08:54 +0700 Subject: [PATCH 146/181] fix(deps): Update dependency io.insert-koin:koin-bom to v4.0.4 (#416) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 05bd1c387e..0ff5516e95 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ sqlite = "2.4.0" sqldelight = "2.0.2" junit = "5.11.3" kermit = "2.0.5" -koin = "4.0.0" +koin = "4.0.4" leakcanary = "2.14" voyager = "1.1.0-beta03" From f1597bd95cd230a53d5959b109893c331cd871e1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:09:09 +0700 Subject: [PATCH 147/181] fix(deps): Update dependency me.zhanghai.android.libarchive:library to v1.1.5 (#417) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0ff5516e95..dcee35addf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,7 +54,7 @@ kotest-assertions = { module = "io.kotest:kotest-assertions-core", version = "5. leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" } leakcanary-plumber = { module = "com.squareup.leakcanary:plumber-android", version.ref = "leakcanary" } -libarchive = { module = "me.zhanghai.android.libarchive:library", version = "1.1.4" } +libarchive = { module = "me.zhanghai.android.libarchive:library", version = "1.1.5" } material = { module = "com.google.android.material:material", version = "1.12.0" } markwon = { module = "io.noties.markwon:core", version = "4.6.2" } From 6f03935c173cd45706283289efed58761c614569 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:09:25 +0700 Subject: [PATCH 148/181] fix(deps): Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.2 (#418) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/kotlinx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index afeeadc0fe..7feb97ba35 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -7,7 +7,7 @@ xml_serialization = "0.90.3" gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } compose-compiler-gradle = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } -coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version = "1.10.1" } +coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version = "1.10.2" } coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" } coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" } coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test" } From 9453c3e808a906acab2e1523dc0ca27b8e8e4810 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:09:41 +0700 Subject: [PATCH 149/181] fix(deps): Update kotlin monorepo to v2.1.21 (#419) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/kotlinx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index 7feb97ba35..83a97e9aac 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "2.1.0" +kotlin = "2.1.21" serialization = "1.7.3" xml_serialization = "0.90.3" From 97339689c6d331b60a8a5b66b98e41c4a88cb3cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:10:01 +0700 Subject: [PATCH 150/181] fix(deps): Update moko to v0.24.5 (#420) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dcee35addf..15ae589508 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ aboutlibraries = "11.2.3" chucker = "3.5.2" flexible-adapter = "c8013533" fast_adapter = "5.7.0" -moko = "0.24.4" +moko = "0.24.5" okhttp = "5.0.0-alpha.14" shizuku = "13.1.5" # FIXME: Uncomment once SQLDelight support KMP AndroidX SQLiteDriver From 8d3cfffa66b8a7b1d8d1316b30325b5c72a9daff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:11:10 +0700 Subject: [PATCH 151/181] fix(deps): Update okhttp monorepo to v5.0.0-alpha.16 (#421) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ahmad Ansori Palembani <46041660+null2264@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 15ae589508..91785ab0dc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ chucker = "3.5.2" flexible-adapter = "c8013533" fast_adapter = "5.7.0" moko = "0.24.5" -okhttp = "5.0.0-alpha.14" +okhttp = "5.0.0-alpha.16" shizuku = "13.1.5" # FIXME: Uncomment once SQLDelight support KMP AndroidX SQLiteDriver #sqlite = "2.5.0-alpha04" From 4b7564e410e74686a9aaa8c8ab9e255eef76d67a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:11:36 +0700 Subject: [PATCH 152/181] chore(deps): Update plugin gradle-versions to v0.52.0 (#422) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 91785ab0dc..d70d82ce93 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -108,7 +108,7 @@ voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", vers aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" } firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "3.0.3" } google-services = { id = "com.google.gms.google-services", version = "4.4.2" } -gradle-versions = { id = "com.github.ben-manes.versions", version = "0.51.0" } +gradle-versions = { id = "com.github.ben-manes.versions", version = "0.52.0" } kotlinter = { id = "org.jmailen.kotlinter", version = "5.0.1" } moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } From ca6bb95b8412df7c017ae308bd6da2e0f51d5b57 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:12:59 +0700 Subject: [PATCH 153/181] chore(deps): Update plugin kotlinter to v5.1.0 (#423) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d70d82ce93..04ac3490ad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -109,7 +109,7 @@ aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "abo firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "3.0.3" } google-services = { id = "com.google.gms.google-services", version = "4.4.2" } gradle-versions = { id = "com.github.ben-manes.versions", version = "0.52.0" } -kotlinter = { id = "org.jmailen.kotlinter", version = "5.0.1" } +kotlinter = { id = "org.jmailen.kotlinter", version = "5.1.0" } moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } From 3867aabff1de7670f8aa697f466442cb281f14c1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:13:20 +0700 Subject: [PATCH 154/181] fix(deps): Update aboutlibraries to v11.6.3 (#424) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 04ac3490ad..7be3621463 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -aboutlibraries = "11.2.3" +aboutlibraries = "11.6.3" chucker = "3.5.2" flexible-adapter = "c8013533" fast_adapter = "5.7.0" From b6e1cabc599344200dc3266ab8f653004097d21d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:13:35 +0700 Subject: [PATCH 155/181] fix(deps): Update dependency androidx.compose:compose-bom to v2025.05.01 (#425) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/compose.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 4c62e2e8da..b2d4949634 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,5 +1,5 @@ [versions] -compose = "2025.01.00" +compose = "2025.05.01" [libraries] bom = { module = "androidx.compose:compose-bom", version.ref = "compose" } From 733fcbba4a75c8d592916548794b69195f8e50da Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:13:50 +0700 Subject: [PATCH 156/181] fix(deps): Update dependency androidx.core:core-ktx to v1.16.0 (#426) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/androidx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 5be34c29c5..0e23030154 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -13,7 +13,7 @@ appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" } browser = { module = "androidx.browser:browser", version = "1.8.0" } biometric = { module = "androidx.biometric:biometric", version = "1.1.0" } cardview = { module = "androidx.cardview:cardview", version = "1.0.0" } -core = { module = "androidx.core:core-ktx", version = "1.15.0" } +core = { module = "androidx.core:core-ktx", version = "1.16.0" } core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" } glance-appwidget = { module = "androidx.glance:glance-appwidget", version = "1.1.1" } layout-constraint = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.1" } From af2be0d2d007afa800c87aff36c08b950044bc38 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:14:07 +0700 Subject: [PATCH 157/181] fix(deps): Update dependency androidx.recyclerview:recyclerview to v1.4.0 (#427) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/androidx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 0e23030154..f6b8c46b0f 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -27,7 +27,7 @@ lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel multidex = { module = "androidx.multidex:multidex", version = "2.0.1" } palette = { module = "androidx.palette:palette", version = "1.0.0" } preference = { module = "androidx.preference:preference-ktx", version = "1.2.1" } -recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.3.2" } +recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.4.0" } sqlite = { module = "androidx.sqlite:sqlite", version = "2.4.0" } webkit = { module = "androidx.webkit:webkit", version = "1.12.0" } work = { module = "androidx.work:work-runtime-ktx", version = "2.10.1" } From 764d52a729ebec12c3668367272c2093daead670 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:16:45 +0700 Subject: [PATCH 158/181] fix(deps): Update dependency androidx.sqlite:sqlite to v2.5.1 (#428) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/androidx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index f6b8c46b0f..ad1f979332 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -28,7 +28,7 @@ multidex = { module = "androidx.multidex:multidex", version = "2.0.1" } palette = { module = "androidx.palette:palette", version = "1.0.0" } preference = { module = "androidx.preference:preference-ktx", version = "1.2.1" } recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.4.0" } -sqlite = { module = "androidx.sqlite:sqlite", version = "2.4.0" } +sqlite = { module = "androidx.sqlite:sqlite", version = "2.5.1" } webkit = { module = "androidx.webkit:webkit", version = "1.12.0" } work = { module = "androidx.work:work-runtime-ktx", version = "2.10.1" } window = { module = "androidx.window:window", version = "1.3.0" } From c05ba1a8fbd401d619c047200cbc29232d75b866 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:16:58 +0700 Subject: [PATCH 159/181] fix(deps): Update dependency androidx.sqlite:sqlite-ktx to v2.5.1 (#429) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7be3621463..c04975cd8f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ okhttp = "5.0.0-alpha.16" shizuku = "13.1.5" # FIXME: Uncomment once SQLDelight support KMP AndroidX SQLiteDriver #sqlite = "2.5.0-alpha04" -sqlite = "2.4.0" +sqlite = "2.5.1" sqldelight = "2.0.2" junit = "5.11.3" kermit = "2.0.5" From ef49bf3321844012fae36ddcd63caeac92c59333 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:20:44 +0700 Subject: [PATCH 160/181] fix(deps): Update dependency androidx.webkit:webkit to v1.13.0 (#430) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/androidx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index ad1f979332..3b3867e031 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -29,7 +29,7 @@ palette = { module = "androidx.palette:palette", version = "1.0.0" } preference = { module = "androidx.preference:preference-ktx", version = "1.2.1" } recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.4.0" } sqlite = { module = "androidx.sqlite:sqlite", version = "2.5.1" } -webkit = { module = "androidx.webkit:webkit", version = "1.12.0" } +webkit = { module = "androidx.webkit:webkit", version = "1.13.0" } work = { module = "androidx.work:work-runtime-ktx", version = "2.10.1" } window = { module = "androidx.window:window", version = "1.3.0" } From 63435b933a1990bbcdfa285a9052b6cea2d348ed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:20:59 +0700 Subject: [PATCH 161/181] fix(deps): Update dependency androidx.window:window to v1.4.0 (#431) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/androidx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 3b3867e031..bffbd09ae9 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -31,7 +31,7 @@ recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.4.0 sqlite = { module = "androidx.sqlite:sqlite", version = "2.5.1" } webkit = { module = "androidx.webkit:webkit", version = "1.13.0" } work = { module = "androidx.work:work-runtime-ktx", version = "2.10.1" } -window = { module = "androidx.window:window", version = "1.3.0" } +window = { module = "androidx.window:window", version = "1.4.0" } [bundles] androidx = [ From 48938f02ddf20e9b106d93aa531466e70c09cce3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:21:18 +0700 Subject: [PATCH 162/181] fix(deps): Update dependency com.getkeepsafe.taptargetview:taptargetview to v1.15.0 (#432) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c04975cd8f..db0a5eb1d0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -95,7 +95,7 @@ sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", subsamplingscaleimageview = { module = "com.github.null2264:subsampling-scale-image-view", version = "f7b674ebdd" } shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizuku" } shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizuku" } -taptargetview = { module = "com.getkeepsafe.taptargetview:taptargetview", version = "1.13.3" } +taptargetview = { module = "com.getkeepsafe.taptargetview:taptargetview", version = "1.15.0" } unifile = { module = "com.github.tachiyomiorg:unifile", version = "a9de196cc7" } viewstatepageradapter = { module = "com.nightlynexus.viewstatepageradapter:viewstatepageradapter", version = "1.1.0" } viewtooltip = { module = "com.github.CarlosEsco:ViewTooltip", version = "f79a8955ef" } # FIXME: Don't depends on this From 9ccdd36c46b8131f4df7ad2b963f4f8454ddf8dc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:24:04 +0700 Subject: [PATCH 163/181] fix(deps): Update dependency com.github.requery:sqlite-android to v3.49.0 (#434) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index db0a5eb1d0..c195e4e936 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -85,7 +85,7 @@ slice = { module = "com.github.mthli:Slice", version = "v1.2" } # FIXME: Uncomment once SQLDelight support KMP AndroidX SQLiteDriver #sqlite = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" } sqlite-ktx = { module = "androidx.sqlite:sqlite-ktx", version.ref = "sqlite" } -sqlite-android = { module = "com.github.requery:sqlite-android", version = "3.45.0" } +sqlite-android = { module = "com.github.requery:sqlite-android", version = "3.49.0" } sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } From 1d49d6596158337aeb655dfd53ea1337fdb1d197 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:24:24 +0700 Subject: [PATCH 164/181] fix(deps): Update dependency com.google.accompanist:accompanist-themeadapter-material3 to v0.36.0 (#435) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c195e4e936..8e4903584a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ coil3 = { module = "io.coil-kt.coil3:coil" } coil3-svg = { module = "io.coil-kt.coil3:coil-svg" } coil3-gif = { module = "io.coil-kt.coil3:coil-gif" } coil3-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp" } -compose-theme-adapter3 = { module = "com.google.accompanist:accompanist-themeadapter-material3", version = "0.33.2-alpha" } +compose-theme-adapter3 = { module = "com.google.accompanist:accompanist-themeadapter-material3", version = "0.36.0" } conductor = { module = "com.bluelinelabs:conductor", version = "4.0.0-preview-4" } conductor-support-preference = { module = "com.github.tachiyomiorg:conductor-support-preference", version = "3.0.0" } conscrypt = { module = "org.conscrypt:conscrypt-android", version = "2.5.2" } From 9f256bb8c662674879d1fb1f3574e4f82fda6788 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:24:40 +0700 Subject: [PATCH 165/181] fix(deps): Update dependency com.google.firebase:firebase-bom to v33.14.0 (#436) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e4903584a..77fdeefe8a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -35,7 +35,7 @@ directionalviewpager = { module = "com.github.tachiyomiorg:DirectionalViewPager" disklrucache = { module = "com.jakewharton:disklrucache", version = "2.0.2" } fastadapter-extensions-binding = { module = "com.mikepenz:fastadapter-extensions-binding", version.ref = "fast_adapter" } fastadapter = { module = "com.mikepenz:fastadapter", version.ref = "fast_adapter" } -firebase = { module = "com.google.firebase:firebase-bom", version = "33.7.0" } +firebase = { module = "com.google.firebase:firebase-bom", version = "33.14.0" } firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" } firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" } flexbox = { module = "com.google.android.flexbox:flexbox", version = "3.0.0" } From a1f6eb65249219f41ff82670bf748579026dcdbf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:24:58 +0700 Subject: [PATCH 166/181] fix(deps): Update dependency com.squareup.okio:okio to v3.12.0 (#437) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 77fdeefe8a..f57cd4c1a4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -69,7 +69,7 @@ mockk = { module = "io.mockk:mockk", version = "1.13.13" } moko-resources = { module = "dev.icerock.moko:resources", version.ref = "moko" } moko-resources-compose = { module = "dev.icerock.moko:resources-compose", version.ref = "moko" } -okio = { module = "com.squareup.okio:okio", version = "3.9.1" } +okio = { module = "com.squareup.okio:okio", version = "3.12.0" } okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" } okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" } okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } From d80b53ba78ba2cb04e4e137cc06cb0e0e699a548 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:25:16 +0700 Subject: [PATCH 167/181] fix(deps): Update dependency io.coil-kt.coil3:coil-bom to v3.2.0 (#438) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f57cd4c1a4..022688e1a1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ aboutlibraries = { module = "com.mikepenz:aboutlibraries-compose-m3", version.re chucker-library-no-op = { module = "com.github.ChuckerTeam.Chucker:library-no-op", version.ref = "chucker" } chucker-library = { module = "com.github.ChuckerTeam.Chucker:library", version.ref = "chucker" } -coil3-bom = { module = "io.coil-kt.coil3:coil-bom", version = "3.0.4" } +coil3-bom = { module = "io.coil-kt.coil3:coil-bom", version = "3.2.0" } coil3 = { module = "io.coil-kt.coil3:coil" } coil3-svg = { module = "io.coil-kt.coil3:coil-svg" } coil3-gif = { module = "io.coil-kt.coil3:coil-gif" } From 4e2c4aef8a2f724623e36a24e165bb2fe6f30aad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:25:31 +0700 Subject: [PATCH 168/181] fix(deps): Update dependency io.mockk:mockk to v1.14.2 (#441) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 022688e1a1..eccaffe317 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,7 +64,7 @@ jsoup = { module = "org.jsoup:jsoup", version = "1.18.3" } junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } junit-android = { module = "androidx.test.ext:junit", version = "1.2.1" } -mockk = { module = "io.mockk:mockk", version = "1.13.13" } +mockk = { module = "io.mockk:mockk", version = "1.14.2" } moko-resources = { module = "dev.icerock.moko:resources", version.ref = "moko" } moko-resources-compose = { module = "dev.icerock.moko:resources-compose", version.ref = "moko" } From a77d315922e1c7d172970c7e40a4fbfd232b206e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:25:49 +0700 Subject: [PATCH 169/181] fix(deps): Update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.4.0 (#442) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/kotlinx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index 83a97e9aac..468c53308f 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -16,7 +16,7 @@ serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serializatio serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization" } serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "xml_serialization" } serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-android", version.ref = "xml_serialization" } -immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version = "0.3.8" } +immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version = "0.4.0" } [bundles] serialization = [ From f68e9df74d9d5630d25af5a2cbf7a537f77c7a50 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:26:05 +0700 Subject: [PATCH 170/181] fix(deps): Update dependency org.jsoup:jsoup to v1.20.1 (#443) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eccaffe317..1edaecae2e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -60,7 +60,7 @@ material = { module = "com.google.android.material:material", version = "1.12.0" markwon = { module = "io.noties.markwon:core", version = "4.6.2" } mpandroidchart = { module = "com.github.PhilJay:MPAndroidChart", version = "v3.1.0" } java-nat-sort = { module = "com.github.gpanther:java-nat-sort", version = "natural-comparator-1.1" } -jsoup = { module = "org.jsoup:jsoup", version = "1.18.3" } +jsoup = { module = "org.jsoup:jsoup", version = "1.20.1" } junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } junit-android = { module = "androidx.test.ext:junit", version = "1.2.1" } From 41662979feadb166f86234fc548b92734e428e33 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:26:24 +0700 Subject: [PATCH 171/181] fix(deps): Update lifecycle to v2.9.0 (#445) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/androidx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index bffbd09ae9..8ca4a27c21 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -1,7 +1,7 @@ [versions] activity = "1.9.3" agp = "8.7.3" -lifecycle = "2.8.7" +lifecycle = "2.9.0" [libraries] gradle = { module = "com.android.tools.build:gradle", version.ref = "agp" } From cd5cdbe746807d5226babfa8996b4367962872ae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:32:55 +0700 Subject: [PATCH 172/181] fix(deps): Update dependency io.github.fornewid:material-motion-compose-core to v1.2.1 (#439) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/compose.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index b2d4949634..86eb7c364f 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -6,7 +6,7 @@ bom = { module = "androidx.compose:compose-bom", version.ref = "compose" } animation = { module = "androidx.compose.animation:animation" } foundation = { module = "androidx.compose.foundation:foundation" } material3 = { module = "androidx.compose.material3:material3" } -material-motion = { module = "io.github.fornewid:material-motion-compose-core", version = "1.0.7" } +material-motion = { module = "io.github.fornewid:material-motion-compose-core", version = "1.2.1" } ui-tooling = { module = "androidx.compose.ui:ui-tooling" } ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } icons = { module = "androidx.compose.material:material-icons-extended" } From 54df9436b8813b370e92adf5838a784fba5e187f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 07:33:22 +0700 Subject: [PATCH 173/181] fix(deps): Update serialization to v1.8.1 (#446) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/kotlinx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index 468c53308f..73ee12979c 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.1.21" -serialization = "1.7.3" +serialization = "1.8.1" xml_serialization = "0.90.3" [libraries] From 43d4d5404dc0af3f1e70264d38ddac1167f1f44e Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sat, 31 May 2025 07:36:21 +0700 Subject: [PATCH 174/181] docs: Sync changelog --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 105ff36b02..abe0f356cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,6 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co ### Other - Refactor Library to utilize Flow even more -- Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.1 - Refactor EmptyView to use Compose - Refactor Reader ChapterTransition to use Compose (@arkon) - [Experimental] Add modified version of LargeTopAppBar that mimic J2K's ExpandedAppBarLayout @@ -50,9 +49,41 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Refactor Library to store LibraryMap instead of flatten list of LibraryItem - LibraryItem abstraction to make it easier to manage - LibraryManga no longer extend MangaImpl -- Update dependency androidx.compose:compose-bom to v2025.01.00 - Update dependency gradle to v8.12 - Update user agent (@Hiirbaf) +- Update serialization to v1.8.1 +- Update dependency io.github.fornewid:material-motion-compose-core to v1.2.1 +- Update lifecycle to v2.9.0 +- Update dependency org.jsoup:jsoup to v1.20.1 +- Update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.4.0 +- Update dependency io.mockk:mockk to v1.14.2 +- Update dependency io.coil-kt.coil3:coil-bom to v3.2.0 +- Update dependency com.squareup.okio:okio to v3.12.0 +- Update dependency com.google.firebase:firebase-bom to v33.14.0 +- Update dependency com.google.accompanist:accompanist-themeadapter-material3 to v0.36.0 +- Update dependency com.github.requery:sqlite-android to v3.49.0 +- Update dependency com.getkeepsafe.taptargetview:taptargetview to v1.15.0 +- Update dependency androidx.window:window to v1.4.0 +- Update dependency androidx.webkit:webkit to v1.13.0 +- Update dependency androidx.sqlite:sqlite-ktx to v2.5.1 +- Update dependency androidx.sqlite:sqlite to v2.5.1 +- Update dependency androidx.recyclerview:recyclerview to v1.4.0 +- Update dependency androidx.core:core-ktx to v1.16.0 +- Update dependency androidx.compose:compose-bom to v2025.05.01 +- Update aboutlibraries to v11.6.3 +- Update plugin kotlinter to v5.1.0 +- Update plugin gradle-versions to v0.52.0 +- Update okhttp monorepo to v5.0.0-alpha.16 +- Update moko to v0.24.5 +- Update kotlin monorepo to v2.1.21 +- Update dependency org.jetbrains.kotlinx:kotlinx-coroutines-bom to v1.10.2 +- Update dependency me.zhanghai.android.libarchive:library to v1.1.5 +- Update dependency io.insert-koin:koin-bom to v4.0.4 +- Update dependency com.android.tools:desugar_jdk_libs to v2.1.5 +- Update dependency androidx.work:work-runtime-ktx to v2.10.1 +- Update dependency androidx.constraintlayout:constraintlayout to v2.2.1 +- Update plugin firebase-crashlytics to v3.0.3 +- Update null2264/actions digest to 363cb9c ## [1.9.7.2] From a04ea9f5ea211d7c35ddb9be0b14a7e97d8f3a3b Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sat, 31 May 2025 11:11:21 +0700 Subject: [PATCH 175/181] chore(deps): Also grab dependency from sonatype just in case --- settings.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.gradle.kts b/settings.gradle.kts index bd2a49e70e..68a592a5f0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,6 +24,7 @@ dependencyResolutionManagement { google() maven("https://jitpack.io") maven("https://plugins.gradle.org/m2/") + maven("https://s01.oss.sonatype.org/content/repositories/releases/") } } From f604e4e25682ab4e9fc8c1d96b34107d57b9db31 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 1 Jun 2025 14:55:19 +0700 Subject: [PATCH 176/181] refactor: Replace Requery's SQLite with AndroidX's new KMP SQLite --- CHANGELOG.md | 4 +- app/build.gradle.kts | 1 - app/src/main/java/yokai/core/di/AppModule.kt | 67 ++++++++------------ gradle/androidx.versions.toml | 3 +- gradle/libs.versions.toml | 23 +++---- 5 files changed, 38 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abe0f356cf..5bcdde586f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Refactor Library to store LibraryMap instead of flatten list of LibraryItem - LibraryItem abstraction to make it easier to manage - LibraryManga no longer extend MangaImpl +- Replace Requery's SQLite with AndroidX's new KMP SQLite - Update dependency gradle to v8.12 - Update user agent (@Hiirbaf) - Update serialization to v1.8.1 @@ -65,8 +66,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Update dependency com.getkeepsafe.taptargetview:taptargetview to v1.15.0 - Update dependency androidx.window:window to v1.4.0 - Update dependency androidx.webkit:webkit to v1.13.0 -- Update dependency androidx.sqlite:sqlite-ktx to v2.5.1 -- Update dependency androidx.sqlite:sqlite to v2.5.1 +- Update androidxSqlite to v2.5.1 - Update dependency androidx.recyclerview:recyclerview to v1.4.0 - Update dependency androidx.core:core-ktx to v1.16.0 - Update dependency androidx.compose:compose-bom to v2025.05.01 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 44eaebc446..55782686bf 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -200,7 +200,6 @@ dependencies { implementation(libs.play.services.gcm) // Database - implementation(libs.sqlite.android) implementation(libs.bundles.sqlite) // Model View Presenter diff --git a/app/src/main/java/yokai/core/di/AppModule.kt b/app/src/main/java/yokai/core/di/AppModule.kt index 70f1f13d92..d7a3bae0ec 100644 --- a/app/src/main/java/yokai/core/di/AppModule.kt +++ b/app/src/main/java/yokai/core/di/AppModule.kt @@ -2,12 +2,19 @@ package yokai.core.di import android.app.Application import androidx.core.content.ContextCompat -import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.driver.bundled.BundledSQLiteDriver +import androidx.sqlite.driver.bundled.SQLITE_OPEN_CREATE +import androidx.sqlite.driver.bundled.SQLITE_OPEN_READWRITE import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.android.AndroidSqliteDriver import co.touchlab.kermit.Logger import com.chuckerteam.chucker.api.ChuckerCollector import com.chuckerteam.chucker.api.ChuckerInterceptor +import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteConfiguration +import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteDatabaseType +import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteDriver +import com.eygraber.sqldelight.androidx.driver.File +import com.eygraber.sqldelight.androidx.driver.SqliteJournalMode +import com.eygraber.sqldelight.androidx.driver.SqliteSync import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.core.storage.AndroidStorageFolderProvider import eu.kanade.tachiyomi.data.cache.ChapterCache @@ -23,7 +30,6 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.util.chapter.ChapterFilter import eu.kanade.tachiyomi.util.manga.MangaShortcutManager -import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory import kotlinx.serialization.json.Json import kotlinx.serialization.protobuf.ProtoBuf import nl.adaptivity.xmlutil.XmlDeclMode @@ -42,46 +48,23 @@ fun appModule(app: Application) = module { single { app } single { - AndroidSqliteDriver( + AndroidxSqliteDriver( + createConnection = { name -> + BundledSQLiteDriver().open(name, SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE) + }, + databaseType = AndroidxSqliteDatabaseType.File(app, "tachiyomi.db"), + configuration = AndroidxSqliteConfiguration().apply { + isForeignKeyConstraintsEnabled = true + journalMode = SqliteJournalMode.WAL + sync = SqliteSync.Normal + }, schema = Database.Schema, - context = app, - name = "tachiyomi.db", - // factory = if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - // // Support database inspector in Android Studio - // FrameworkSQLiteOpenHelperFactory() - // } else { - // RequerySQLiteOpenHelperFactory() - // }, - factory = RequerySQLiteOpenHelperFactory(), - callback = object : AndroidSqliteDriver.Callback(Database.Schema) { - override fun onOpen(db: SupportSQLiteDatabase) { - super.onOpen(db) - setPragma(db, "foreign_keys = ON") - setPragma(db, "journal_mode = WAL") - setPragma(db, "synchronous = NORMAL") - } - - private fun setPragma(db: SupportSQLiteDatabase, pragma: String) { - val cursor = db.query("PRAGMA $pragma") - cursor.moveToFirst() - cursor.close() - } - - // Not sure if this is still needed, but just in case - override fun onConfigure(db: SupportSQLiteDatabase) { - db.setForeignKeyConstraintsEnabled(true) - } - - override fun onCreate(db: SupportSQLiteDatabase) { - Logger.d { "Creating new database..." } - super.onCreate(db) - } - - override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { - if (oldVersion < newVersion) { - Logger.d { "Upgrading database from $oldVersion to $newVersion" } - super.onUpgrade(db, oldVersion, newVersion) - } + onCreate = { + Logger.d { "Creating new database..." } + }, + onUpdate = { oldVersion, newVersion -> + if (oldVersion < newVersion) { + Logger.d { "Upgrading database from $oldVersion to $newVersion" } } }, ) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 8ca4a27c21..865f80d446 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -28,7 +28,6 @@ multidex = { module = "androidx.multidex:multidex", version = "2.0.1" } palette = { module = "androidx.palette:palette", version = "1.0.0" } preference = { module = "androidx.preference:preference-ktx", version = "1.2.1" } recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.4.0" } -sqlite = { module = "androidx.sqlite:sqlite", version = "2.5.1" } webkit = { module = "androidx.webkit:webkit", version = "1.13.0" } work = { module = "androidx.work:work-runtime-ktx", version = "2.10.1" } window = { module = "androidx.window:window", version = "1.4.0" } @@ -38,7 +37,7 @@ androidx = [ "activity", "activity-compose", "annotation", "appcompat", "browser", "biometric", "cardview", "core", "core-splashscreen", "layout-constraint", "glance-appwidget", "lifecycle-common", "lifecycle-livedata", "lifecycle-process", "lifecycle-runtime", "lifecycle-viewmodel", "lifecycle-viewmodel-compose", "multidex", - "palette", "preference", "recyclerview", "sqlite", "layout-swiperefresh", "webkit", "work", "window" + "palette", "preference", "recyclerview", "layout-swiperefresh", "webkit", "work", "window" ] [plugins] diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1edaecae2e..7dde266dd0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,9 +6,9 @@ fast_adapter = "5.7.0" moko = "0.24.5" okhttp = "5.0.0-alpha.16" shizuku = "13.1.5" -# FIXME: Uncomment once SQLDelight support KMP AndroidX SQLiteDriver -#sqlite = "2.5.0-alpha04" -sqlite = "2.5.1" +androidxSqlite = "2.5.1" +# FIXME: Stay at 2.0.2 until 2.1.1 released because dialect is borked +# REF: https://github.com/sqldelight/sqldelight/issues/5758 sqldelight = "2.0.2" junit = "5.11.3" kermit = "2.0.5" @@ -82,13 +82,12 @@ rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" } rxandroid = { module = "io.reactivex:rxandroid", version = "1.2.1" } slice = { module = "com.github.mthli:Slice", version = "v1.2" } -# FIXME: Uncomment once SQLDelight support KMP AndroidX SQLiteDriver -#sqlite = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" } -sqlite-ktx = { module = "androidx.sqlite:sqlite-ktx", version.ref = "sqlite" } -sqlite-android = { module = "com.github.requery:sqlite-android", version = "3.49.0" } +# SQLite interface +sqlite = { module = "androidx.sqlite:sqlite", version.ref = "androidxSqlite" } +sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "androidxSqlite" } sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } -sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } +sqldelight-androidx-driver = { module = "com.eygraber:sqldelight-androidx-driver", version = "0.0.12" } sqldelight-android-paging = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "sqldelight" } sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" } @@ -114,13 +113,11 @@ moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } [bundles] -db = [ "sqldelight-coroutines" ] -db-android = [ "sqldelight-android-driver", "sqldelight-android-paging" ] +db = [ "sqldelight-coroutines", "sqldelight-androidx-driver" ] +db-android = [ "sqldelight-android-paging" ] coil = [ "coil3", "coil3-svg", "coil3-gif", "coil3-okhttp" ] logging = [ "kermit" ] -# FIXME: Uncomment once SQLDelight support KMP AndroidX SQLiteDriver -#sqlite = [ "sqlite", "sqlite-ktx" ] -sqlite = [ "sqlite-ktx" ] +sqlite = [ "sqlite", "sqlite-bundled" ] test = [ "junit-api", "kotest-assertions", "mockk" ] test-android = [ "junit-android" ] test-runtime = [ "junit-engine" ] From f90e2a1425414c394dd3d7ef815ada33a858225c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Jun 2025 16:43:59 +0700 Subject: [PATCH 177/181] fix(deps): Update dependency io.github.pdvrieze.xmlutil:core-android to v0.91.1 (#440) * fix(deps): Update dependency io.github.pdvrieze.xmlutil:core-android to v0.91.1 * fix: Fix build --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ahmad Ansori Palembani --- gradle/kotlinx.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index 73ee12979c..b7fe860fd0 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.1.21" serialization = "1.8.1" -xml_serialization = "0.90.3" +xml_serialization = "0.91.1" [libraries] gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } @@ -15,7 +15,7 @@ serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-jso serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "serialization" } serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization" } serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "xml_serialization" } -serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-android", version.ref = "xml_serialization" } +serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization", version.ref = "xml_serialization" } immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version = "0.4.0" } [bundles] From 7ac42d55456b7d46e4722de01722e6648c6468e5 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 1 Jun 2025 16:44:45 +0700 Subject: [PATCH 178/181] docs: Sync changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bcdde586f..4e7ba588e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Update dependency androidx.constraintlayout:constraintlayout to v2.2.1 - Update plugin firebase-crashlytics to v3.0.3 - Update null2264/actions digest to 363cb9c +- Update dependency io.github.pdvrieze.xmlutil:core-android to v0.91.1 ## [1.9.7.2] From abbe60647353bb674ad4b7950a16c0501ccda763 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Sun, 1 Jun 2025 18:05:35 +0700 Subject: [PATCH 179/181] revert: "refactor: Replace Requery's SQLite with AndroidX's new KMP SQLite" This reverts commit f604e4e25682ab4e9fc8c1d96b34107d57b9db31. --- CHANGELOG.md | 4 +- app/build.gradle.kts | 1 + app/src/main/java/yokai/core/di/AppModule.kt | 67 ++++++++++++-------- gradle/androidx.versions.toml | 3 +- gradle/libs.versions.toml | 23 ++++--- 5 files changed, 60 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e7ba588e2..d6f3944187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,7 +49,6 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Refactor Library to store LibraryMap instead of flatten list of LibraryItem - LibraryItem abstraction to make it easier to manage - LibraryManga no longer extend MangaImpl -- Replace Requery's SQLite with AndroidX's new KMP SQLite - Update dependency gradle to v8.12 - Update user agent (@Hiirbaf) - Update serialization to v1.8.1 @@ -66,7 +65,8 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Update dependency com.getkeepsafe.taptargetview:taptargetview to v1.15.0 - Update dependency androidx.window:window to v1.4.0 - Update dependency androidx.webkit:webkit to v1.13.0 -- Update androidxSqlite to v2.5.1 +- Update dependency androidx.sqlite:sqlite-ktx to v2.5.1 +- Update dependency androidx.sqlite:sqlite to v2.5.1 - Update dependency androidx.recyclerview:recyclerview to v1.4.0 - Update dependency androidx.core:core-ktx to v1.16.0 - Update dependency androidx.compose:compose-bom to v2025.05.01 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 55782686bf..44eaebc446 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -200,6 +200,7 @@ dependencies { implementation(libs.play.services.gcm) // Database + implementation(libs.sqlite.android) implementation(libs.bundles.sqlite) // Model View Presenter diff --git a/app/src/main/java/yokai/core/di/AppModule.kt b/app/src/main/java/yokai/core/di/AppModule.kt index d7a3bae0ec..70f1f13d92 100644 --- a/app/src/main/java/yokai/core/di/AppModule.kt +++ b/app/src/main/java/yokai/core/di/AppModule.kt @@ -2,19 +2,12 @@ package yokai.core.di import android.app.Application import androidx.core.content.ContextCompat -import androidx.sqlite.driver.bundled.BundledSQLiteDriver -import androidx.sqlite.driver.bundled.SQLITE_OPEN_CREATE -import androidx.sqlite.driver.bundled.SQLITE_OPEN_READWRITE +import androidx.sqlite.db.SupportSQLiteDatabase import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.android.AndroidSqliteDriver import co.touchlab.kermit.Logger import com.chuckerteam.chucker.api.ChuckerCollector import com.chuckerteam.chucker.api.ChuckerInterceptor -import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteConfiguration -import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteDatabaseType -import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteDriver -import com.eygraber.sqldelight.androidx.driver.File -import com.eygraber.sqldelight.androidx.driver.SqliteJournalMode -import com.eygraber.sqldelight.androidx.driver.SqliteSync import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.core.storage.AndroidStorageFolderProvider import eu.kanade.tachiyomi.data.cache.ChapterCache @@ -30,6 +23,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.util.chapter.ChapterFilter import eu.kanade.tachiyomi.util.manga.MangaShortcutManager +import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory import kotlinx.serialization.json.Json import kotlinx.serialization.protobuf.ProtoBuf import nl.adaptivity.xmlutil.XmlDeclMode @@ -48,23 +42,46 @@ fun appModule(app: Application) = module { single { app } single { - AndroidxSqliteDriver( - createConnection = { name -> - BundledSQLiteDriver().open(name, SQLITE_OPEN_READWRITE or SQLITE_OPEN_CREATE) - }, - databaseType = AndroidxSqliteDatabaseType.File(app, "tachiyomi.db"), - configuration = AndroidxSqliteConfiguration().apply { - isForeignKeyConstraintsEnabled = true - journalMode = SqliteJournalMode.WAL - sync = SqliteSync.Normal - }, + AndroidSqliteDriver( schema = Database.Schema, - onCreate = { - Logger.d { "Creating new database..." } - }, - onUpdate = { oldVersion, newVersion -> - if (oldVersion < newVersion) { - Logger.d { "Upgrading database from $oldVersion to $newVersion" } + context = app, + name = "tachiyomi.db", + // factory = if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // // Support database inspector in Android Studio + // FrameworkSQLiteOpenHelperFactory() + // } else { + // RequerySQLiteOpenHelperFactory() + // }, + factory = RequerySQLiteOpenHelperFactory(), + callback = object : AndroidSqliteDriver.Callback(Database.Schema) { + override fun onOpen(db: SupportSQLiteDatabase) { + super.onOpen(db) + setPragma(db, "foreign_keys = ON") + setPragma(db, "journal_mode = WAL") + setPragma(db, "synchronous = NORMAL") + } + + private fun setPragma(db: SupportSQLiteDatabase, pragma: String) { + val cursor = db.query("PRAGMA $pragma") + cursor.moveToFirst() + cursor.close() + } + + // Not sure if this is still needed, but just in case + override fun onConfigure(db: SupportSQLiteDatabase) { + db.setForeignKeyConstraintsEnabled(true) + } + + override fun onCreate(db: SupportSQLiteDatabase) { + Logger.d { "Creating new database..." } + super.onCreate(db) + } + + override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { + if (oldVersion < newVersion) { + Logger.d { "Upgrading database from $oldVersion to $newVersion" } + super.onUpgrade(db, oldVersion, newVersion) + } } }, ) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 865f80d446..8ca4a27c21 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -28,6 +28,7 @@ multidex = { module = "androidx.multidex:multidex", version = "2.0.1" } palette = { module = "androidx.palette:palette", version = "1.0.0" } preference = { module = "androidx.preference:preference-ktx", version = "1.2.1" } recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.4.0" } +sqlite = { module = "androidx.sqlite:sqlite", version = "2.5.1" } webkit = { module = "androidx.webkit:webkit", version = "1.13.0" } work = { module = "androidx.work:work-runtime-ktx", version = "2.10.1" } window = { module = "androidx.window:window", version = "1.4.0" } @@ -37,7 +38,7 @@ androidx = [ "activity", "activity-compose", "annotation", "appcompat", "browser", "biometric", "cardview", "core", "core-splashscreen", "layout-constraint", "glance-appwidget", "lifecycle-common", "lifecycle-livedata", "lifecycle-process", "lifecycle-runtime", "lifecycle-viewmodel", "lifecycle-viewmodel-compose", "multidex", - "palette", "preference", "recyclerview", "layout-swiperefresh", "webkit", "work", "window" + "palette", "preference", "recyclerview", "sqlite", "layout-swiperefresh", "webkit", "work", "window" ] [plugins] diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7dde266dd0..1edaecae2e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,9 +6,9 @@ fast_adapter = "5.7.0" moko = "0.24.5" okhttp = "5.0.0-alpha.16" shizuku = "13.1.5" -androidxSqlite = "2.5.1" -# FIXME: Stay at 2.0.2 until 2.1.1 released because dialect is borked -# REF: https://github.com/sqldelight/sqldelight/issues/5758 +# FIXME: Uncomment once SQLDelight support KMP AndroidX SQLiteDriver +#sqlite = "2.5.0-alpha04" +sqlite = "2.5.1" sqldelight = "2.0.2" junit = "5.11.3" kermit = "2.0.5" @@ -82,12 +82,13 @@ rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" } rxandroid = { module = "io.reactivex:rxandroid", version = "1.2.1" } slice = { module = "com.github.mthli:Slice", version = "v1.2" } -# SQLite interface -sqlite = { module = "androidx.sqlite:sqlite", version.ref = "androidxSqlite" } -sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "androidxSqlite" } +# FIXME: Uncomment once SQLDelight support KMP AndroidX SQLiteDriver +#sqlite = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" } +sqlite-ktx = { module = "androidx.sqlite:sqlite-ktx", version.ref = "sqlite" } +sqlite-android = { module = "com.github.requery:sqlite-android", version = "3.49.0" } sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } -sqldelight-androidx-driver = { module = "com.eygraber:sqldelight-androidx-driver", version = "0.0.12" } +sqldelight-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-android-paging = { module = "app.cash.sqldelight:androidx-paging3-extensions", version.ref = "sqldelight" } sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" } @@ -113,11 +114,13 @@ moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } [bundles] -db = [ "sqldelight-coroutines", "sqldelight-androidx-driver" ] -db-android = [ "sqldelight-android-paging" ] +db = [ "sqldelight-coroutines" ] +db-android = [ "sqldelight-android-driver", "sqldelight-android-paging" ] coil = [ "coil3", "coil3-svg", "coil3-gif", "coil3-okhttp" ] logging = [ "kermit" ] -sqlite = [ "sqlite", "sqlite-bundled" ] +# FIXME: Uncomment once SQLDelight support KMP AndroidX SQLiteDriver +#sqlite = [ "sqlite", "sqlite-ktx" ] +sqlite = [ "sqlite-ktx" ] test = [ "junit-api", "kotest-assertions", "mockk" ] test-android = [ "junit-android" ] test-runtime = [ "junit-engine" ] From 7f83c117be344175532be5233fc85559e4bcb16d Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 2 Jun 2025 10:04:20 +0700 Subject: [PATCH 180/181] chore: Sync project [skip ci] --- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/issue_report.yml | 2 +- CHANGELOG.md | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index bae9c3625b..545636880b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -35,7 +35,7 @@ body: required: true - label: If this is an issue with an extension, or a request for an extension, I should be contacting the extensions repository's maintainer/support for help. required: true - - label: I have updated the app to version **[1.9.7.2](https://github.com/null2264/yokai/releases/latest)**. + - label: I have updated the app to version **[1.9.7.3](https://github.com/null2264/yokai/releases/latest)**. required: true - label: I have checked through the app settings for my feature. required: true diff --git a/.github/ISSUE_TEMPLATE/issue_report.yml b/.github/ISSUE_TEMPLATE/issue_report.yml index 3c3b5d4590..a1f0593f0d 100644 --- a/.github/ISSUE_TEMPLATE/issue_report.yml +++ b/.github/ISSUE_TEMPLATE/issue_report.yml @@ -100,7 +100,7 @@ body: required: true - label: I have tried the [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/). required: true - - label: I have updated the app to version **[1.9.7.2](https://github.com/null2264/yokai/releases/latest)**. + - label: I have updated the app to version **[1.9.7.3](https://github.com/null2264/yokai/releases/latest)**. required: true - label: I have updated all installed extensions. required: true diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f3944187..95cea8eab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,11 @@ The format is simplified version of [Keep a Changelog](https://keepachangelog.co - Update null2264/actions digest to 363cb9c - Update dependency io.github.pdvrieze.xmlutil:core-android to v0.91.1 +## [1.9.7.3] + +### Fixes +- More `Comparison method violates its general contract!` crash prevention + ## [1.9.7.2] ### Fixes From 17879ddc5abb6e2c82a85861cbfa17ffaa32c625 Mon Sep 17 00:00:00 2001 From: Ahmad Ansori Palembani Date: Mon, 2 Jun 2025 13:52:46 +0700 Subject: [PATCH 181/181] chore: Bump version code --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 44eaebc446..dc23c131b0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -49,7 +49,7 @@ val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") android { defaultConfig { applicationId = "eu.kanade.tachiyomi" - versionCode = 157 + versionCode = 158 versionName = _versionName testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled = true