diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 70ec0d3b7..044602cf4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -242,6 +242,9 @@ + { + val sources = sourcesRepository.getEnabledSources() + if (sources.isEmpty()) { + return emptyFlow() + } + val semaphore = Semaphore(MAX_PARALLELISM) + return channelFlow { + for (source in sources) { + val repository = mangaRepositoryFactory.create(source) + if (!repository.isSearchSupported) { + continue + } + launch { + val list = runCatchingCancellable { + semaphore.withPermit { + repository.getList(offset = 0, filter = MangaListFilter.Search(manga.title)) + } + }.getOrDefault(emptyList()) + for (item in list) { + if (item != manga && item.matches(manga)) { + send(item) + } + } + } + } + }.map { + runCatchingCancellable { + mangaRepositoryFactory.create(it.source).getDetails(it) + }.getOrDefault(it) + } + } + + private fun Manga.matches(ref: Manga): Boolean { + return matchesTitles(title, ref.title) || + matchesTitles(title, ref.altTitle) || + matchesTitles(altTitle, ref.title) || + matchesTitles(altTitle, ref.altTitle) + + } + + private fun matchesTitles(a: String?, b: String?): Boolean { + return !a.isNullOrEmpty() && !b.isNullOrEmpty() && a.almostEquals(b, MATCH_THRESHOLD) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/domain/MigrateUseCase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/domain/MigrateUseCase.kt new file mode 100644 index 000000000..4a061af87 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/domain/MigrateUseCase.kt @@ -0,0 +1,127 @@ +package org.koitharu.kotatsu.alternatives.domain + +import androidx.room.withTransaction +import org.koitharu.kotatsu.core.db.MangaDatabase +import org.koitharu.kotatsu.core.model.getPreferredBranch +import org.koitharu.kotatsu.core.parser.MangaDataRepository +import org.koitharu.kotatsu.core.parser.MangaRepository +import org.koitharu.kotatsu.details.domain.DetailsLoadUseCase +import org.koitharu.kotatsu.details.domain.ProgressUpdateUseCase +import org.koitharu.kotatsu.favourites.data.FavouriteEntity +import org.koitharu.kotatsu.history.data.HistoryEntity +import org.koitharu.kotatsu.history.data.PROGRESS_NONE +import org.koitharu.kotatsu.history.data.toMangaHistory +import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaChapter +import org.koitharu.kotatsu.parsers.util.runCatchingCancellable +import javax.inject.Inject + +class MigrateUseCase @Inject constructor( + private val mangaRepositoryFactory: MangaRepository.Factory, + private val mangaDataRepository: MangaDataRepository, + private val database: MangaDatabase, + private val progressUpdateUseCase: ProgressUpdateUseCase, + private val useCase: DetailsLoadUseCase +) { + + suspend operator fun invoke(oldManga: Manga, newManga: Manga) { + val oldDetails = if (oldManga.chapters.isNullOrEmpty()) { + runCatchingCancellable { + mangaRepositoryFactory.create(oldManga.source).getDetails(oldManga) + }.getOrDefault(oldManga) + } else { + oldManga + } + val newDetails = if (newManga.chapters.isNullOrEmpty()) { + mangaRepositoryFactory.create(newManga.source).getDetails(newManga) + } else { + newManga + } + mangaDataRepository.storeManga(newDetails) + database.withTransaction { + // replace favorites + val favoritesDao = database.getFavouritesDao() + val oldFavourite = favoritesDao.find(oldDetails.id) + if (oldFavourite != null) { + favoritesDao.delete(oldManga.id) + for (f in oldFavourite.categories) { + val e = FavouriteEntity( + mangaId = newManga.id, + categoryId = f.categoryId.toLong(), + sortKey = f.sortKey, + createdAt = f.createdAt, + deletedAt = 0, + ) + favoritesDao.upsert(e) + } + } + // replace history + val historyDao = database.getHistoryDao() + val oldHistory = historyDao.find(oldDetails.id) + if (oldHistory != null) { + val newHistory = makeNewHistory(oldDetails, newDetails, oldHistory) + historyDao.delete(oldDetails.id) + historyDao.upsert(newHistory) + } + } + progressUpdateUseCase(newManga) + } + + private fun makeNewHistory( + oldManga: Manga, + newManga: Manga, + history: HistoryEntity, + ): HistoryEntity { + if (oldManga.chapters.isNullOrEmpty()) { // probably broken manga/source + val branch = newManga.getPreferredBranch(null) + val chapters = checkNotNull(newManga.getChapters(branch)) + return HistoryEntity( + mangaId = newManga.id, + createdAt = history.createdAt, + updatedAt = System.currentTimeMillis(), + chapterId = chapters[(chapters.lastIndex * history.percent).toInt()].id, + page = history.page, + scroll = history.scroll, + percent = history.percent, + deletedAt = 0, + chaptersCount = chapters.size, + ) + } + val branch = oldManga.getPreferredBranch(history.toMangaHistory()) + val oldChapters = checkNotNull(oldManga.getChapters(branch)) + var index = oldChapters.indexOfFirst { it.id == history.chapterId } + if (index < 0) { + index = (oldChapters.size * history.percent).toInt() + } + val newChapters = checkNotNull(newManga.chapters).groupBy { it.branch } + val newBranch = if (newChapters.containsKey(branch)) { + branch + } else { + newManga.getPreferredBranch(null) + } + val newChapterId = checkNotNull(newChapters[newBranch]).let { + val oldChapter = oldChapters[index] + it.findByNumber(oldChapter.volume, oldChapter.number) ?: it.getOrNull(index) ?: it.last() + }.id + + return HistoryEntity( + mangaId = newManga.id, + createdAt = history.createdAt, + updatedAt = System.currentTimeMillis(), + chapterId = newChapterId, + page = history.page, + scroll = history.scroll, + percent = PROGRESS_NONE, + deletedAt = 0, + chaptersCount = checkNotNull(newChapters[newBranch]).size, + ) + } + + private fun List.findByNumber(volume: Int, number: Float): MangaChapter? { + return if (number <= 0f) { + null + } else { + firstOrNull { it.volume == volume && it.number == number } + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativeAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativeAD.kt new file mode 100644 index 000000000..9c2f4f7a0 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativeAD.kt @@ -0,0 +1,88 @@ +package org.koitharu.kotatsu.alternatives.ui + +import android.text.style.ForegroundColorSpan +import androidx.core.content.ContextCompat +import androidx.core.text.buildSpannedString +import androidx.core.text.inSpans +import androidx.lifecycle.LifecycleOwner +import coil.ImageLoader +import coil.request.ImageRequest +import coil.transform.CircleCropTransformation +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.parser.favicon.faviconUri +import org.koitharu.kotatsu.core.ui.image.ChipIconTarget +import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver +import org.koitharu.kotatsu.core.ui.image.TrimTransformation +import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter +import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.util.ext.enqueueWith +import org.koitharu.kotatsu.core.util.ext.newImageRequest +import org.koitharu.kotatsu.core.util.ext.source +import org.koitharu.kotatsu.databinding.ItemMangaAlternativeBinding +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback +import org.koitharu.kotatsu.list.ui.model.ListModel +import kotlin.math.sign +import com.google.android.material.R as materialR + +fun alternativeAD( + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, + listener: OnListItemClickListener, +) = adapterDelegateViewBinding( + { inflater, parent -> ItemMangaAlternativeBinding.inflate(inflater, parent, false) }, +) { + + val colorGreen = ContextCompat.getColor(context, R.color.common_green) + val colorRed = ContextCompat.getColor(context, R.color.common_red) + val clickListener = AdapterDelegateClickListenerAdapter(this, listener) + itemView.setOnClickListener(clickListener) + binding.buttonMigrate.setOnClickListener(clickListener) + binding.chipSource.setOnClickListener(clickListener) + + bind { payloads -> + binding.textViewTitle.text = item.manga.title + binding.textViewSubtitle.text = buildSpannedString { + append(context.resources.getQuantityString(R.plurals.chapters, item.chaptersCount, item.chaptersCount)) + when (item.chaptersDiff.sign) { + -1 -> inSpans(ForegroundColorSpan(colorRed)) { + append(" ▼ ") + append(item.chaptersDiff.toString()) + } + + 1 -> inSpans(ForegroundColorSpan(colorGreen)) { + append(" ▲ +") + append(item.chaptersDiff.toString()) + } + } + } + binding.progressView.setPercent(item.progress, ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads) + binding.chipSource.also { chip -> + chip.text = item.manga.source.title + ImageRequest.Builder(context) + .data(item.manga.source.faviconUri()) + .lifecycle(lifecycleOwner) + .crossfade(false) + .size(context.resources.getDimensionPixelSize(materialR.dimen.m3_chip_icon_size)) + .target(ChipIconTarget(chip)) + .placeholder(R.drawable.ic_web) + .fallback(R.drawable.ic_web) + .error(R.drawable.ic_web) + .source(item.manga.source) + .transformations(CircleCropTransformation()) + .allowRgb565(true) + .enqueueWith(coil) + } + binding.imageViewCover.newImageRequest(lifecycleOwner, item.manga.coverUrl)?.run { + size(CoverSizeResolver(binding.imageViewCover)) + placeholder(R.drawable.ic_placeholder) + fallback(R.drawable.ic_placeholder) + error(R.drawable.ic_error_placeholder) + transformations(TrimTransformation()) + allowRgb565(true) + tag(item.manga) + source(item.manga.source) + enqueueWith(coil) + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesActivity.kt new file mode 100644 index 000000000..03d099a54 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesActivity.kt @@ -0,0 +1,114 @@ +package org.koitharu.kotatsu.alternatives.ui + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.activity.viewModels +import androidx.core.graphics.Insets +import androidx.core.view.updatePadding +import coil.ImageLoader +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver +import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga +import org.koitharu.kotatsu.core.parser.MangaIntent +import org.koitharu.kotatsu.core.ui.BaseActivity +import org.koitharu.kotatsu.core.ui.BaseListAdapter +import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED +import org.koitharu.kotatsu.core.util.ext.observe +import org.koitharu.kotatsu.core.util.ext.observeEvent +import org.koitharu.kotatsu.databinding.ActivityAlternativesBinding +import org.koitharu.kotatsu.details.ui.DetailsActivity +import org.koitharu.kotatsu.list.ui.adapter.ListItemType +import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration +import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD +import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD +import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD +import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.search.ui.SearchActivity +import javax.inject.Inject + +@AndroidEntryPoint +class AlternativesActivity : BaseActivity(), + OnListItemClickListener { + + @Inject + lateinit var coil: ImageLoader + + private val viewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(ActivityAlternativesBinding.inflate(layoutInflater)) + supportActionBar?.run { + setDisplayHomeAsUpEnabled(true) + subtitle = viewModel.manga.title + } + val listAdapter = BaseListAdapter() + .addDelegate(ListItemType.MANGA_LIST_DETAILED, alternativeAD(coil, this, this)) + .addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, this, null)) + .addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD()) + .addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) + with(viewBinding.recyclerView) { + setHasFixedSize(true) + addItemDecoration(TypedListSpacingDecoration(context, addHorizontalPadding = false)) + adapter = listAdapter + } + + viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null)) + viewModel.content.observe(this, listAdapter) + viewModel.onMigrated.observeEvent(this) { + Toast.makeText(this, R.string.migration_completed, Toast.LENGTH_SHORT).show() + startActivity(DetailsActivity.newIntent(this, it)) + finishAfterTransition() + } + } + + override fun onWindowInsetsChanged(insets: Insets) { + viewBinding.root.updatePadding( + left = insets.left, + right = insets.right, + ) + viewBinding.recyclerView.updatePadding( + bottom = insets.bottom + viewBinding.recyclerView.paddingTop, + ) + } + + override fun onItemClick(item: MangaAlternativeModel, view: View) { + when (view.id) { + R.id.chip_source -> startActivity(SearchActivity.newIntent(this, item.manga.source, viewModel.manga.title)) + R.id.button_migrate -> confirmMigration(item.manga) + else -> startActivity(DetailsActivity.newIntent(this, item.manga)) + } + } + + private fun confirmMigration(target: Manga) { + MaterialAlertDialogBuilder(this, DIALOG_THEME_CENTERED) + .setIcon(R.drawable.ic_replace) + .setTitle(R.string.manga_migration) + .setMessage( + getString( + R.string.migrate_confirmation, + viewModel.manga.title, + viewModel.manga.source.title, + target.title, + target.source.title, + ), + ) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.migrate) { _, _ -> + viewModel.migrate(target) + }.show() + } + + companion object { + + fun newIntent(context: Context, manga: Manga) = Intent(context, AlternativesActivity::class.java) + .putExtra(MangaIntent.KEY_MANGA, ParcelableManga(manga)) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesViewModel.kt new file mode 100644 index 000000000..b8111042d --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesViewModel.kt @@ -0,0 +1,94 @@ +package org.koitharu.kotatsu.alternatives.ui + +import androidx.lifecycle.SavedStateHandle +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEmpty +import kotlinx.coroutines.flow.runningFold +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.alternatives.domain.AlternativesUseCase +import org.koitharu.kotatsu.alternatives.domain.MigrateUseCase +import org.koitharu.kotatsu.core.model.chaptersCount +import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga +import org.koitharu.kotatsu.core.parser.MangaIntent +import org.koitharu.kotatsu.core.parser.MangaRepository +import org.koitharu.kotatsu.core.ui.BaseViewModel +import org.koitharu.kotatsu.core.util.ext.MutableEventFlow +import org.koitharu.kotatsu.core.util.ext.call +import org.koitharu.kotatsu.core.util.ext.require +import org.koitharu.kotatsu.list.domain.ListExtraProvider +import org.koitharu.kotatsu.list.ui.model.EmptyState +import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.list.ui.model.LoadingFooter +import org.koitharu.kotatsu.list.ui.model.LoadingState +import org.koitharu.kotatsu.parsers.model.Manga +import javax.inject.Inject + +@HiltViewModel +class AlternativesViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + private val mangaRepositoryFactory: MangaRepository.Factory, + private val alternativesUseCase: AlternativesUseCase, + private val migrateUseCase: MigrateUseCase, + private val extraProvider: ListExtraProvider, +) : BaseViewModel() { + + val manga = savedStateHandle.require(MangaIntent.KEY_MANGA).manga + + val onMigrated = MutableEventFlow() + val content = MutableStateFlow>(listOf(LoadingState)) + private var migrationJob: Job? = null + + init { + launchJob(Dispatchers.Default) { + val ref = mangaRepositoryFactory.create(manga.source).getDetails(manga) + val refCount = ref.chaptersCount() + alternativesUseCase(manga) + .map { + MangaAlternativeModel( + manga = it, + progress = extraProvider.getProgress(it.id), + referenceChapters = refCount, + ) + }.runningFold>(listOf(LoadingState)) { acc, item -> + acc.filterIsInstance() + item + LoadingFooter() + }.onEmpty { + emit( + listOf( + EmptyState( + icon = R.drawable.ic_empty_common, + textPrimary = R.string.nothing_found, + textSecondary = R.string.text_search_holder_secondary, + actionStringRes = 0, + ), + ), + ) + }.collect { + content.value = it + } + } + } + + fun migrate(target: Manga) { + if (migrationJob?.isActive == true) { + return + } + migrationJob = launchLoadingJob(Dispatchers.Default) { + migrateUseCase(manga, target) + onMigrated.call(target) + } + } + + private suspend fun mapList(list: List, refCount: Int): List { + return list.map { + MangaAlternativeModel( + manga = it, + progress = extraProvider.getProgress(it.id), + referenceChapters = refCount, + ) + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/MangaAlternativeModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/MangaAlternativeModel.kt new file mode 100644 index 000000000..18571c17a --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/MangaAlternativeModel.kt @@ -0,0 +1,21 @@ +package org.koitharu.kotatsu.alternatives.ui + +import org.koitharu.kotatsu.core.model.chaptersCount +import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.parsers.model.Manga + +data class MangaAlternativeModel( + val manga: Manga, + val progress: Float, + private val referenceChapters: Int, +) : ListModel { + + val chaptersCount = manga.chaptersCount() + + val chaptersDiff: Int + get() = if (referenceChapters == 0 || chaptersCount == 0) 0 else chaptersCount - referenceChapters + + override fun areItemsTheSame(other: ListModel): Boolean { + return other is MangaAlternativeModel && other.manga.id == manga.id + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt index 8cf08b944..b3b591f7f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt @@ -131,3 +131,19 @@ fun MangaChapter.formatNumber(): String? { } return chaptersNumberFormat.format(number.toDouble()) } + +fun Manga.chaptersCount(): Int { + if (chapters.isNullOrEmpty()) { + return 0 + } + val counters = MutableObjectIntMap() + var max = 0 + chapters?.forEach { x -> + val c = counters.getOrDefault(x.branch, 0) + 1 + counters[x.branch] = c + if (max < c) { + max = c + } + } + return max +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/ChipIconTarget.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/ChipIconTarget.kt new file mode 100644 index 000000000..82b002b86 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/ChipIconTarget.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.core.ui.image + +import android.graphics.drawable.Drawable +import coil.target.GenericViewTarget +import com.google.android.material.chip.Chip + +class ChipIconTarget(override val view: Chip) : GenericViewTarget() { + + override var drawable: Drawable? + get() = view.chipIcon + set(value) { + view.chipIcon = value + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt index 0c9be69b9..e544accd9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt @@ -14,6 +14,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.launch import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.alternatives.ui.AlternativesActivity import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener @@ -41,6 +42,7 @@ class DetailsMenuProvider( menu.findItem(R.id.action_save).isVisible = manga?.source != null && manga.source != MangaSource.LOCAL menu.findItem(R.id.action_delete).isVisible = manga?.source == MangaSource.LOCAL menu.findItem(R.id.action_browser).isVisible = manga?.source != MangaSource.LOCAL + menu.findItem(R.id.action_alternatives).isVisible = manga?.source != MangaSource.LOCAL menu.findItem(R.id.action_shortcut).isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(activity) menu.findItem(R.id.action_scrobbling).isVisible = viewModel.isScrobblingAvailable menu.findItem(R.id.action_online).isVisible = viewModel.remoteManga.value != null @@ -103,6 +105,12 @@ class DetailsMenuProvider( } } + R.id.action_alternatives -> { + viewModel.manga.value?.let { + activity.startActivity(AlternativesActivity.newIntent(activity, it)) + } + } + R.id.action_stats -> { viewModel.manga.value?.let { MangaStatsSheet.show(activity.supportFragmentManager, it) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt index 534092ebd..fc5a482c4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt @@ -178,6 +178,7 @@ class FavouriteCategoriesActivity : } } + @Deprecated("") companion object { fun newIntent(context: Context) = Intent(context, FavouriteCategoriesActivity::class.java) diff --git a/app/src/main/res/drawable/ic_replace.xml b/app/src/main/res/drawable/ic_replace.xml new file mode 100644 index 000000000..c7ee3eddd --- /dev/null +++ b/app/src/main/res/drawable/ic_replace.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/layout/activity_alternatives.xml b/app/src/main/res/layout/activity_alternatives.xml new file mode 100644 index 000000000..a9a1b8e4b --- /dev/null +++ b/app/src/main/res/layout/activity_alternatives.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_manga_alternative.xml b/app/src/main/res/layout/item_manga_alternative.xml new file mode 100644 index 000000000..7b7e8c622 --- /dev/null +++ b/app/src/main/res/layout/item_manga_alternative.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/opt_details.xml b/app/src/main/res/menu/opt_details.xml index c525f7c5c..36990cfb8 100644 --- a/app/src/main/res/menu/opt_details.xml +++ b/app/src/main/res/menu/opt_details.xml @@ -49,6 +49,12 @@ android:title="@string/find_similar" app:showAsAction="never" /> + + #66311B92 #FB8C00 #222222 + #81C784 + #E57373 #191C1C diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 22dd1c970..6ea2264d8 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -25,6 +25,8 @@ #99B39DDB #E65100 #FFFFFF + #388E3C + #D32F2F #E4FFFA diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52f610b0f..0aa7ee55a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,603 +1,603 @@ - Kotatsu - Local storage - Favourites - History - An error occurred - Network error - Details - Chapters - List - Detailed list - Grid - List mode - Settings - Manga sources - Loading… - Computing… - Chapter %1$d of %2$d - Close - Try again - Clear history - Nothing found - No history yet - Read - No favourites yet - Favourite this - New category - Add - Save - Share - Create shortcut… - Share %s - Search - Search manga - Downloading… - Processing… - Downloaded - Downloads - Name - Popular - Updated - Newest - Rating - Sorting order - Filter - Theme - Light - Dark - + Kotatsu + Local storage + Favourites + History + An error occurred + Network error + Details + Chapters + List + Detailed list + Grid + List mode + Settings + Manga sources + Loading… + Computing… + Chapter %1$d of %2$d + Close + Try again + Clear history + Nothing found + No history yet + Read + No favourites yet + Favourite this + New category + Add + Save + Share + Create shortcut… + Share %s + Search + Search manga + Downloading… + Processing… + Downloaded + Downloads + Name + Popular + Updated + Newest + Rating + Sorting order + Filter + Theme + Light + Dark + Follow system - Pages - Clear - Clear all reading history permanently? - Remove - \"%s\" deleted from local storage - Save page - Saved - Share image - Import - Delete - This operation is not supported - Either pick a ZIP or CBZ file. - No description - Clear page cache - B|kB|MB|GB|TB - Standard - Webtoon - Read mode - Grid size - Search on %s - Delete manga - Permanently delete \"%s\" from device? - Reader settings - Switch pages - Edge taps - Volume buttons - Continue - Error - Clear thumbnails cache - Clear search history - Cleared - Gestures only - Internal storage - External storage - Domain - A new version of the app is available - Open in web browser - This manga has %s. Save all of it? - Save - Notifications - %1$d of %2$d on - New chapters - Download - Notifications settings - Notification sound - LED indicator - Vibration - Favourite categories - Remove - It\'s kind of empty here… - Try to reformulate the query. - What you read will be displayed here - Find what to read in the «Explore» section - Your manga will be displayed here - Find what to read in the «Explore» section - Save something first - Save something from an online catalog or import it from a file. - Shelf - Recent - Page animation - Downloads folder - Not available - No available storage - Other storage - Done - All favourites - Empty category - Read later - Updates - New chapters of what you are reading are shown here - Search results - New version: %s - Size: %s - Clear updates feed - Cleared - Rotate screen - Update - Feed update will start soon - Look for updates - Don\'t check - Enter password - Wrong password - Protect the app - Ask for password when starting Kotatsu - Repeat the password - Mismatching passwords - About - Version %s - Check for updates - No updates available - Right-to-left - New category - Scale mode - Fit center - Fit to height - Fit to width - Keep at start - Black - Uses less power on AMOLED screens - Backup and restore - Create data backup - Restore from backup - Restored - Preparing… - File not found - All data was restored - The data was restored, but there are errors - You can create backup of your history and favourites and restore it - Just now - Yesterday - Long ago - Group - Today - Tap to try again - The chosen configuration will be remembered for this manga - Silent - CAPTCHA required - Solve - Clear cookies - All cookies were removed - Clear feed - Clear all update history permanently? - Check for new chapters - Reverse - Sign in - Sign in to view this content - Default: %s - Next - Enter a password to start the app with - Confirm - The password must be 4 characters or more - Remove all recent search queries permanently? - Welcome - Backup saved - Some devices have different system behavior, which may break background tasks. - Read more - Queued - The chapter is missing - Translate this app - Translation - Authorized - Logging in on %s is not supported - You will be logged out from all sources - Genres - Finished - Ongoing - Default - Exclude NSFW manga from history - Numbered pages - Used sources - Available sources - Screenshot policy - Allow - Block on NSFW - Always block - Suggestions - Enable suggestions - Suggest manga based on your preferences - All data is only analyzed locally on this device and never sent anywhere. - Start reading manga and you will get personalized suggestions - Do not suggest NSFW manga - Enabled - Disabled - Unable to load genres list - Reset filter - Select languages which you want to read manga. You can change it later in settings. - Never - Only on Wi-Fi - Always - Preload pages - Logged in as %s - 18+ - Various languages - Find chapter - No chapters in this manga - %1$s%% - Appearance - Suggestions updating - Exclude genres - Specify genres that you do not want to see in the suggestions - Delete selected items from device permanently? - Removal completed - Shikimori - AniList - Download slowdown - Helps avoid blocking your IP address - Saved manga processing - Chapters will be removed in the background - Canceled - Account already exists - Back - Synchronization - Sync your data - Enter your email to continue - Hide - New manga sources are available - Check for new chapters and notify about it - You will receive notifications about updates of manga you are reading - You will not receive notifications but new chapters will be highlighted in the lists - Enable notifications - Name - Edit - Edit category - Tracking - No favourite categories - Log out - Add bookmark - Remove bookmark - Bookmarks - Bookmark removed - Bookmark added - Undo - Removed from history - DNS over HTTPS - Default mode - Autodetect reader mode - Automatically detect if manga is webtoon - Disable battery optimization - Helps with background updates checks - Something went wrong. Please submit a bug report to the developers to help us fix it. - Send - Planned - Reading - Re-reading - Completed - On hold - Dropped - Disable all - Use fingerprint if available - Manga from your favourites - Your recently read manga - Report - Show reading progress indicators - Data deletion - Show percentage read in history and favourites - Manga marked as NSFW will never be added to the history and your progress will not be saved - Can help in case of some issues. All authorizations will be invalidated - Show all - Invalid domain - Select range - Clear all history - Last 2 hours - History cleared - Manage - No bookmarks yet - You can create bookmark while reading manga - Bookmarks removed - No manga sources - Enable manga sources to read manga online - Random - Are you sure you want to delete the selected favorite categories?\nAll manga in it will be lost and this cannot be undone. - Reorder - Empty - Explore - Press "Back" again to exit - Press "Back" twice to exit the app - Exit confirmation - Saved manga - Pages cache - Other cache - Storage usage - Available - %s - %s - Removed from favourites - Options - Content not found or removed - %1$s · %2$s - Incognito mode - No chapters - Automatic scroll - Ch. %1$d/%2$d Pg. %3$d/%4$d - Show information bar in reader - Comics archive - Folder with images - Importing manga - Import completed - You can delete the original file from storage to save space - Import will start soon - Feed - Error details:<br><tt>%1$s</tt><br><br>1. Try to <a href="%2$s">open manga in a web browser</a> to ensure it is available on its source<br>2. Make sure you are using the <a href="kotatsu://about">latest version of Kotatsu</a><br>3. If it is available, send an error report to the developers. - Show recent manga shortcuts - Make recent manga available by long pressing on application icon - Tapping on the right edge, or pressing the right key, always switches to the next page. - Ergonomic reader control - Color correction - Brightness - Contrast - Reset - The chosen color settings will be remembered for this manga - Save or discard unsaved changes\? - Discard - No space left on device - Show page switching slider - Webtoon zoom - Different languages - Network is not available - Turn on Wi-Fi or mobile network to read manga online - Server side error (%1$d). Please try again later - Also clear information about new chapters - Compact - MyAnimeList - Source disabled - Content preloading - Mark as current - Language - Share logs - Enable logging - Record some actions for debug purposes. Don\'t turn it on if you\'re not sure what you\'re doing - Show suspicious content - Dynamic - Color scheme - Show in grid view - Miku - Asuka - Mion - Rikka - Sakura - Mamimi - Kanade - There is nothing here - To track reading progress, select Menu → Track on the manga details screen. - Services - Allow unstable updates - Receive notifications about unstable builds - Download started - Got it - Tap and hold on an item to reorder them - UserAgent header - Please restart the application to apply these changes - You can select one or more .cbz or .zip files, each file will be recognized as a separate manga. - You can select a directory with archives or images. Each archive (or subdirectory) will be recognized as a chapter. - Speed - Import a previously created backup of user data - Show on the Shelf - You can sign in into an existing account or create a new one - Find similar - Synchronization settings - Server address - You can use a self-hosted synchronization server or a default one. Don\'t change this if you\'re not sure what you\'re doing. - Ignore SSL errors - Choose mirror automatically - Automatically switch domains for manga sources on errors if mirrors are available - Pause - Resume - Paused - + Pages + Clear + Clear all reading history permanently? + Remove + \"%s\" deleted from local storage + Save page + Saved + Share image + Import + Delete + This operation is not supported + Either pick a ZIP or CBZ file. + No description + Clear page cache + B|kB|MB|GB|TB + Standard + Webtoon + Read mode + Grid size + Search on %s + Delete manga + Permanently delete \"%s\" from device? + Reader settings + Switch pages + Edge taps + Volume buttons + Continue + Error + Clear thumbnails cache + Clear search history + Cleared + Gestures only + Internal storage + External storage + Domain + A new version of the app is available + Open in web browser + This manga has %s. Save all of it? + Save + Notifications + %1$d of %2$d on + New chapters + Download + Notifications settings + Notification sound + LED indicator + Vibration + Favourite categories + Remove + It\'s kind of empty here… + Try to reformulate the query. + What you read will be displayed here + Find what to read in the «Explore» section + Your manga will be displayed here + Find what to read in the «Explore» section + Save something first + Save something from an online catalog or import it from a file. + Shelf + Recent + Page animation + Downloads folder + Not available + No available storage + Other storage + Done + All favourites + Empty category + Read later + Updates + New chapters of what you are reading are shown here + Search results + New version: %s + Size: %s + Clear updates feed + Cleared + Rotate screen + Update + Feed update will start soon + Look for updates + Don\'t check + Enter password + Wrong password + Protect the app + Ask for password when starting Kotatsu + Repeat the password + Mismatching passwords + About + Version %s + Check for updates + No updates available + Right-to-left + New category + Scale mode + Fit center + Fit to height + Fit to width + Keep at start + Black + Uses less power on AMOLED screens + Backup and restore + Create data backup + Restore from backup + Restored + Preparing… + File not found + All data was restored + The data was restored, but there are errors + You can create backup of your history and favourites and restore it + Just now + Yesterday + Long ago + Group + Today + Tap to try again + The chosen configuration will be remembered for this manga + Silent + CAPTCHA required + Solve + Clear cookies + All cookies were removed + Clear feed + Clear all update history permanently? + Check for new chapters + Reverse + Sign in + Sign in to view this content + Default: %s + Next + Enter a password to start the app with + Confirm + The password must be 4 characters or more + Remove all recent search queries permanently? + Welcome + Backup saved + Some devices have different system behavior, which may break background tasks. + Read more + Queued + The chapter is missing + Translate this app + Translation + Authorized + Logging in on %s is not supported + You will be logged out from all sources + Genres + Finished + Ongoing + Default + Exclude NSFW manga from history + Numbered pages + Used sources + Available sources + Screenshot policy + Allow + Block on NSFW + Always block + Suggestions + Enable suggestions + Suggest manga based on your preferences + All data is only analyzed locally on this device and never sent anywhere. + Start reading manga and you will get personalized suggestions + Do not suggest NSFW manga + Enabled + Disabled + Unable to load genres list + Reset filter + Select languages which you want to read manga. You can change it later in settings. + Never + Only on Wi-Fi + Always + Preload pages + Logged in as %s + 18+ + Various languages + Find chapter + No chapters in this manga + %1$s%% + Appearance + Suggestions updating + Exclude genres + Specify genres that you do not want to see in the suggestions + Delete selected items from device permanently? + Removal completed + Shikimori + AniList + Download slowdown + Helps avoid blocking your IP address + Saved manga processing + Chapters will be removed in the background + Canceled + Account already exists + Back + Synchronization + Sync your data + Enter your email to continue + Hide + New manga sources are available + Check for new chapters and notify about it + You will receive notifications about updates of manga you are reading + You will not receive notifications but new chapters will be highlighted in the lists + Enable notifications + Name + Edit + Edit category + Tracking + No favourite categories + Log out + Add bookmark + Remove bookmark + Bookmarks + Bookmark removed + Bookmark added + Undo + Removed from history + DNS over HTTPS + Default mode + Autodetect reader mode + Automatically detect if manga is webtoon + Disable battery optimization + Helps with background updates checks + Something went wrong. Please submit a bug report to the developers to help us fix it. + Send + Planned + Reading + Re-reading + Completed + On hold + Dropped + Disable all + Use fingerprint if available + Manga from your favourites + Your recently read manga + Report + Show reading progress indicators + Data deletion + Show percentage read in history and favourites + Manga marked as NSFW will never be added to the history and your progress will not be saved + Can help in case of some issues. All authorizations will be invalidated + Show all + Invalid domain + Select range + Clear all history + Last 2 hours + History cleared + Manage + No bookmarks yet + You can create bookmark while reading manga + Bookmarks removed + No manga sources + Enable manga sources to read manga online + Random + Are you sure you want to delete the selected favorite categories?\nAll manga in it will be lost and this cannot be undone. + Reorder + Empty + Explore + Press "Back" again to exit + Press "Back" twice to exit the app + Exit confirmation + Saved manga + Pages cache + Other cache + Storage usage + Available + %s - %s + Removed from favourites + Options + Content not found or removed + %1$s · %2$s + Incognito mode + No chapters + Automatic scroll + Ch. %1$d/%2$d Pg. %3$d/%4$d + Show information bar in reader + Comics archive + Folder with images + Importing manga + Import completed + You can delete the original file from storage to save space + Import will start soon + Feed + Error details:<br><tt>%1$s</tt><br><br>1. Try to <a href="%2$s">open manga in a web browser</a> to ensure it is available on its source<br>2. Make sure you are using the <a href="kotatsu://about">latest version of Kotatsu</a><br>3. If it is available, send an error report to the developers. + Show recent manga shortcuts + Make recent manga available by long pressing on application icon + Tapping on the right edge, or pressing the right key, always switches to the next page. + Ergonomic reader control + Color correction + Brightness + Contrast + Reset + The chosen color settings will be remembered for this manga + Save or discard unsaved changes\? + Discard + No space left on device + Show page switching slider + Webtoon zoom + Different languages + Network is not available + Turn on Wi-Fi or mobile network to read manga online + Server side error (%1$d). Please try again later + Also clear information about new chapters + Compact + MyAnimeList + Source disabled + Content preloading + Mark as current + Language + Share logs + Enable logging + Record some actions for debug purposes. Don\'t turn it on if you\'re not sure what you\'re doing + Show suspicious content + Dynamic + Color scheme + Show in grid view + Miku + Asuka + Mion + Rikka + Sakura + Mamimi + Kanade + There is nothing here + To track reading progress, select Menu → Track on the manga details screen. + Services + Allow unstable updates + Receive notifications about unstable builds + Download started + Got it + Tap and hold on an item to reorder them + UserAgent header + Please restart the application to apply these changes + You can select one or more .cbz or .zip files, each file will be recognized as a separate manga. + You can select a directory with archives or images. Each archive (or subdirectory) will be recognized as a chapter. + Speed + Import a previously created backup of user data + Show on the Shelf + You can sign in into an existing account or create a new one + Find similar + Synchronization settings + Server address + You can use a self-hosted synchronization server or a default one. Don\'t change this if you\'re not sure what you\'re doing. + Ignore SSL errors + Choose mirror automatically + Automatically switch domains for manga sources on errors if mirrors are available + Pause + Resume + Paused + Remove completed - Cancel all - Download only via Wi-Fi - Stop downloading when switching to a mobile network - Suggestion: %s - Sometimes show notifications with suggested manga - More - Enable - No thanks - All active downloads will be cancelled, partially downloaded data will be lost - Your downloads history will be permanently deleted - You don\'t have any downloads - Downloads have been resumed - Downloads have been paused - Downloads have been removed - Downloads have been cancelled - Do you want to receive personalized manga suggestions? - Translations - WebView not available: check if WebView provider is installed - Clear network cache - Type - Address - Port - Proxy - Invalid value - Kitsu - Enter your email and password to continue - %1$s (%2$s) - Downloaded - Images optimization proxy - Use the wsrv.nl service to reduce traffic usage and speed up image loading if possible - Invert colors - Username - Password - Authorization (optional) - Invalid port number - Network - Data and privacy - Restore previously created backup - Allow zoom in gesture in webtoon mode - Show the current time and reading progress at the top of the screen - Show page numbers in bottom corner - Animate page switching - Press and hold the Read button to see more options - Clear cookies for specified domain only. In most cases will invalidate authorization - All chapters with translation %s - The whole manga - First %s - Next unread %s - All unread chapters - All unread chapters (%s) - Select chapters manually - Custom directory - Pick custom directory - You have no access to this file or directory - Local manga directories - Description - This month - Voice search - Related manga - Light - Dark - White - Black - Background - Data was not restored - Make sure you have selected the correct backup file - Manage categories - Do not update suggestions using metered network connections - Do not check for new chapters using metered network connections - Enter manga title, genre or source name - Progress - Added - View list - Show - %s requires a captcha to be resolved to work properly - Languages - Unknown - In progress - Disable NSFW - Too many requests. Try again later - Show a list of related manga. In some cases it may be inaccurate or missing - Advanced - Default section - Manga list - Invalid data is returned or file is corrupted - On device - Directories - Main screen sections - No more items can be added - To top - Moved to top - Zoom out - Zoom in - Show zoom buttons - Whether to show zoom control buttons in the bottom right corner - Keep screen on - Do not turn the screen off while you\'re reading manga - Dropped - Reduces banding, but may impact performance - 32-bit color mode - Suggest new sources after app update - Prompt to enable newly added sources after updating the application - List options - Relevance - Categories - Online variant - Periodic backups - Backup creation frequency - Every day - Every 2 days - Once per week - Twice per month - Once per month - Enable periodic backups - Backups output directory - Last successful backup: %s - x%.1f - Lock screen rotation - Manga - Hentai - Comics - Other - %1$s, %2$s - Sources catalog - Source enabled - There are no sources available in this section, or all of it might have been already added.\nStay tuned - No available manga sources found by your query - Catalog - Manage sources - Manual - Available: %1$d - Disable NSFW sources and hide adult manga from list if possible - Paused - Reduce memory consumption (beta) - Reduce offscreen pages quality to use less memory - State - Filtering by multiple genres is not supported by this manga source - Filtering by multiple states is not supported by this manga source - Search is not supported by this manga source - You can enable download slowdown for each manga source individually in the source settings if you are having problems with server-side blocking - Skip - Grayscale - Globally - This manga - These settings can be applied globally or only to the current manga. If applied globally, individual settings will not be overridden. - Apply - Filtering by both genres and locale is not supported by this source - Filtering by both genres and states is not supported by this source - Start typing the genre name - Might help with getting the download started if you have any issues with it - Please select which content sources you would like to enable. This can also be configured later in settings - Login to sync account - Restore - Backup date: %s - Upcoming - Name reversed - Content rating - Exclude genres - Safe - Suggestive - Adult - Default tab - Mark as completed - Mark selected manga as completely read?\n\nWarning: current reading progress will be lost. - This category was hidden from the main screen and is accessible through Menu → Manage categories - Approximate reading time - Approximate remaining time - %1$s %2$s - Volume %d - Unknown volume - Your reading progress will not be saved - Vertical - Last read - Two pages - Show menu - Show/hide UI - Previous chapter - Next chapter - Previous page - Next page - Reader actions - Configure actions for tappable screen areas - Enable volume buttons - Use volume buttons for switching pages - Tap action - Long tap action - None - Reset settings to default values? This action cannot be undone. - Use two pages layout on landscape orientation (beta) - Default webtoon zoom out - Fullscreen mode - Hide system status and navigation bars - %1$d/%2$d - Show estimated reading time - The time estimation value may be inaccurate - Suggestions feature is disabled - Checking for new chapters is disabled - Show labels in navigation bar - Saving pages - Ask for the destination dir every time - Default page save directory - Remove from history + Cancel all + Download only via Wi-Fi + Stop downloading when switching to a mobile network + Suggestion: %s + Sometimes show notifications with suggested manga + More + Enable + No thanks + All active downloads will be cancelled, partially downloaded data will be lost + Your downloads history will be permanently deleted + You don\'t have any downloads + Downloads have been resumed + Downloads have been paused + Downloads have been removed + Downloads have been cancelled + Do you want to receive personalized manga suggestions? + Translations + WebView not available: check if WebView provider is installed + Clear network cache + Type + Address + Port + Proxy + Invalid value + Kitsu + Enter your email and password to continue + %1$s (%2$s) + Downloaded + Images optimization proxy + Use the wsrv.nl service to reduce traffic usage and speed up image loading if possible + Invert colors + Username + Password + Authorization (optional) + Invalid port number + Network + Data and privacy + Restore previously created backup + Allow zoom in gesture in webtoon mode + Show the current time and reading progress at the top of the screen + Show page numbers in bottom corner + Animate page switching + Press and hold the Read button to see more options + Clear cookies for specified domain only. In most cases will invalidate authorization + All chapters with translation %s + The whole manga + First %s + Next unread %s + All unread chapters + All unread chapters (%s) + Select chapters manually + Custom directory + Pick custom directory + You have no access to this file or directory + Local manga directories + Description + This month + Voice search + Related manga + Light + Dark + White + Black + Background + Data was not restored + Make sure you have selected the correct backup file + Manage categories + Do not update suggestions using metered network connections + Do not check for new chapters using metered network connections + Enter manga title, genre or source name + Progress + Added + View list + Show + %s requires a captcha to be resolved to work properly + Languages + Unknown + In progress + Disable NSFW + Too many requests. Try again later + Show a list of related manga. In some cases it may be inaccurate or missing + Advanced + Default section + Manga list + Invalid data is returned or file is corrupted + On device + Directories + Main screen sections + No more items can be added + To top + Moved to top + Zoom out + Zoom in + Show zoom buttons + Whether to show zoom control buttons in the bottom right corner + Keep screen on + Do not turn the screen off while you\'re reading manga + Dropped + Reduces banding, but may impact performance + 32-bit color mode + Suggest new sources after app update + Prompt to enable newly added sources after updating the application + List options + Relevance + Categories + Online variant + Periodic backups + Backup creation frequency + Every day + Every 2 days + Once per week + Twice per month + Once per month + Enable periodic backups + Backups output directory + Last successful backup: %s + x%.1f + Lock screen rotation + Manga + Hentai + Comics + Other + %1$s, %2$s + Sources catalog + Source enabled + There are no sources available in this section, or all of it might have been already added.\nStay tuned + No available manga sources found by your query + Catalog + Manage sources + Manual + Available: %1$d + Disable NSFW sources and hide adult manga from list if possible + Paused + Reduce memory consumption (beta) + Reduce offscreen pages quality to use less memory + State + Filtering by multiple genres is not supported by this manga source + Filtering by multiple states is not supported by this manga source + Search is not supported by this manga source + You can enable download slowdown for each manga source individually in the source settings if you are having problems with server-side blocking + Skip + Grayscale + Globally + This manga + These settings can be applied globally or only to the current manga. If applied globally, individual settings will not be overridden. + Apply + Filtering by both genres and locale is not supported by this source + Filtering by both genres and states is not supported by this source + Start typing the genre name + Might help with getting the download started if you have any issues with it + Please select which content sources you would like to enable. This can also be configured later in settings + Login to sync account + Restore + Backup date: %s + Upcoming + Name reversed + Content rating + Exclude genres + Safe + Suggestive + Adult + Default tab + Mark as completed + Mark selected manga as completely read?\n\nWarning: current reading progress will be lost. + This category was hidden from the main screen and is accessible through Menu → Manage categories + Approximate reading time + Approximate remaining time + %1$s %2$s + Volume %d + Unknown volume + Your reading progress will not be saved + Vertical + Last read + Two pages + Show menu + Show/hide UI + Previous chapter + Next chapter + Previous page + Next page + Reader actions + Configure actions for tappable screen areas + Enable volume buttons + Use volume buttons for switching pages + Tap action + Long tap action + None + Reset settings to default values? This action cannot be undone. + Use two pages layout on landscape orientation (beta) + Default webtoon zoom out + Fullscreen mode + Hide system status and navigation bars + %1$d/%2$d + Show estimated reading time + The time estimation value may be inaccurate + Suggestions feature is disabled + Checking for new chapters is disabled + Show labels in navigation bar + Saving pages + Ask for the destination dir every time + Default page save directory + Remove from history Location Preferred download format Automatic @@ -618,4 +618,9 @@ Three months There are no statistics for the selected period Pages read: %s + Alternatives + Migrate + Manga \"%1$s\" from \"%2$s\" will be replaced with \"%3$s\" from \"%4$s\" in your history and favorites (if present) + Manga migration + Migration completed