Compare commits

...

13 Commits

Author SHA1 Message Date
e37455e790 Update parsers and add support for Upcoming state 2023-12-28 17:48:00 +02:00
Paper Jack
36259ba901 Translated using Weblate (Italian)
Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Paper Jack <paperjack@tutanota.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2023-12-28 16:50:47 +02:00
Nicolò Bertazzo
5b041b9a49 Translated using Weblate (Italian)
Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Nicolò Bertazzo <n.bertazzo@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2023-12-28 16:50:47 +02:00
1734e888d6 Move new sources tip to catalog 2023-12-26 20:24:14 +02:00
9108646cea Update parsers 2023-12-25 19:52:37 +02:00
Макар Разин
c6d1cf2f72 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (546 of 546 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (546 of 546 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-12-25 13:26:51 +02:00
Anon
a317236cb0 Translated using Weblate (Serbian)
Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2023-12-25 13:26:51 +02:00
Deivinni Silva
3703c07a98 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Deivinni Silva <deivinnimds3656@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2023-12-25 13:26:51 +02:00
b7b32dd447 Update parsers 2023-12-25 13:18:28 +02:00
c103386dc5 Fix crash on file size compution 2023-12-25 10:01:43 +02:00
a9d6ee4a95 Update dependencies 2023-12-25 09:57:01 +02:00
71f2c91e5a Improve sources catalog 2023-12-23 08:44:09 +02:00
b878f358ff Improve explore screen 2023-12-23 07:26:31 +02:00
36 changed files with 568 additions and 305 deletions

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@
/.idea/compiler.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/ktlint-plugin.xml
/.idea/assetWizardSettings.xml
/.idea/kotlinScripting.xml
/.idea/kotlinc.xml

View File

@@ -16,8 +16,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 34
versionCode = 607
versionName = '6.5.1'
versionCode = 609
versionName = '6.5.3'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {
@@ -82,7 +82,7 @@ afterEvaluate {
}
dependencies {
//noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:86a2b95141') {
implementation('com.github.KotatsuApp:kotatsu-parsers:a228d71d57') {
exclude group: 'org.json', module: 'json'
}
@@ -125,8 +125,8 @@ dependencies {
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
implementation 'com.google.dagger:hilt-android:2.49'
kapt 'com.google.dagger:hilt-compiler:2.49'
implementation 'com.google.dagger:hilt-android:2.50'
kapt 'com.google.dagger:hilt-compiler:2.50'
implementation 'androidx.hilt:hilt-work:1.1.0'
kapt 'androidx.hilt:hilt-compiler:1.1.0'
@@ -155,6 +155,6 @@ dependencies {
androidTestImplementation 'androidx.room:room-testing:2.6.1'
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.0'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.49'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.49'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.50'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.50'
}

View File

@@ -61,13 +61,20 @@ class CaptchaNotifier(
override fun onError(request: ImageRequest, result: ErrorResult) {
super.onError(request, result)
val e = result.throwable
if (e is CloudFlareProtectedException) {
if (e is CloudFlareProtectedException && request.parameters.value<Boolean>(PARAM_IGNORE_CAPTCHA) != true) {
notify(e)
}
}
private companion object {
companion object {
fun ImageRequest.Builder.ignoreCaptchaErrors() = setParameter(
key = PARAM_IGNORE_CAPTCHA,
value = true,
memoryCacheKey = null,
)
private const val PARAM_IGNORE_CAPTCHA = "ignore_captcha"
private const val CHANNEL_ID = "captcha"
private const val TAG = CHANNEL_ID
}

View File

@@ -12,6 +12,7 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.mapToSet
import com.google.android.material.R as materialR
@JvmName("mangaIds")
fun Collection<Manga>.ids() = mapToSet { it.id }
@@ -42,15 +43,17 @@ val MangaState.titleResId: Int
MangaState.FINISHED -> R.string.state_finished
MangaState.ABANDONED -> R.string.state_abandoned
MangaState.PAUSED -> R.string.state_paused
MangaState.UPCOMING -> R.string.state_upcoming
}
@get:DrawableRes
val MangaState.iconResId: Int
get() = when (this) {
MangaState.ONGOING -> R.drawable.ic_state_ongoing
MangaState.ONGOING -> R.drawable.ic_play
MangaState.FINISHED -> R.drawable.ic_state_finished
MangaState.ABANDONED -> R.drawable.ic_state_abandoned
MangaState.PAUSED -> R.drawable.ic_action_pause
MangaState.UPCOMING -> materialR.drawable.ic_clock_black_24dp
}
fun Manga.findChapter(id: Long): MangaChapter? {

View File

@@ -1,17 +1,23 @@
package org.koitharu.kotatsu.core.model
import android.content.Context
import android.graphics.Color
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.text.style.RelativeSizeSpan
import android.text.style.SuperscriptSpan
import androidx.annotation.StringRes
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.getDisplayName
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.toLocale
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.toTitleCase
import java.util.Locale
fun MangaSource.getLocaleTitle(): String? {
val lc = Locale(locale ?: return null)
return lc.getDisplayLanguage(lc).toTitleCase(lc)
}
import com.google.android.material.R as materialR
fun MangaSource(name: String): MangaSource {
MangaSource.entries.forEach {
@@ -33,6 +39,24 @@ val ContentType.titleResId
fun MangaSource.getSummary(context: Context): String {
val type = context.getString(contentType.titleResId)
val locale = getLocaleTitle() ?: context.getString(R.string.various_languages)
val locale = locale?.toLocale().getDisplayName(context)
return context.getString(R.string.source_summary_pattern, type, locale)
}
fun MangaSource.getTitle(context: Context): CharSequence = if (isNsfw()) {
buildSpannedString {
append(title)
append(' ')
appendNsfwLabel(context)
}
} else {
title
}
private fun SpannableStringBuilder.appendNsfwLabel(context: Context) = inSpans(
ForegroundColorSpan(context.getThemeColor(materialR.attr.colorError, Color.RED)),
RelativeSizeSpan(0.74f),
SuperscriptSpan(),
) {
append(context.getString(R.string.nsfw))
}

View File

@@ -12,4 +12,5 @@ val SortOrder.titleRes: Int
SortOrder.RATING -> R.string.by_rating
SortOrder.NEWEST -> R.string.newest
SortOrder.ALPHABETICAL -> R.string.by_name
SortOrder.ALPHABETICAL_DESC -> R.string.by_name_reverse
}

View File

@@ -70,7 +70,7 @@ class WindowInsetsDelegate : OnApplyWindowInsetsListener, View.OnLayoutChangeLis
lastInsets = null
}
interface WindowInsetsListener {
fun interface WindowInsetsListener {
fun onWindowInsetsChanged(insets: Insets)
}

View File

@@ -20,12 +20,13 @@ inline fun <T> LocaleListCompat.mapToSet(block: (Locale) -> T): Set<T> {
fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw NoSuchElementException()
fun String?.getLocaleDisplayName(context: Context): String {
fun String.toLocale() = Locale(this)
fun Locale?.getDisplayName(context: Context): String {
if (this == null) {
return context.getString(R.string.various_languages)
}
val lc = Locale(this)
return lc.getDisplayLanguage(lc).toTitleCase(lc)
return getDisplayLanguage(this).toTitleCase(this)
}
private class LocaleListCompatIterator(private val list: LocaleListCompat) : ListIterator<Locale> {

View File

@@ -30,6 +30,8 @@ import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
import org.koitharu.kotatsu.bookmarks.ui.sheet.BookmarksSheet
import org.koitharu.kotatsu.core.model.countChaptersByBranch
import org.koitharu.kotatsu.core.model.iconResId
import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.ui.BaseFragment
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
@@ -66,7 +68,6 @@ import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver
import org.koitharu.kotatsu.main.ui.owners.NoModalBottomSheetOwner
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
@@ -181,28 +182,13 @@ class DetailsFragment :
ratingBar.isVisible = false
}
when (manga.state) {
MangaState.FINISHED -> infoLayout.textViewState.apply {
textAndVisible = resources.getString(R.string.state_finished)
drawableTop = ContextCompat.getDrawable(context, R.drawable.ic_state_finished)
infoLayout.textViewState.apply {
manga.state?.let { state ->
textAndVisible = resources.getString(state.titleResId)
drawableTop = ContextCompat.getDrawable(context, state.iconResId)
} ?: run {
isVisible = false
}
MangaState.ONGOING -> infoLayout.textViewState.apply {
textAndVisible = resources.getString(R.string.state_ongoing)
drawableTop = ContextCompat.getDrawable(context, R.drawable.ic_state_ongoing)
}
MangaState.ABANDONED -> infoLayout.textViewState.apply {
textAndVisible = resources.getString(R.string.state_abandoned)
drawableTop = ContextCompat.getDrawable(context, R.drawable.ic_state_abandoned)
}
MangaState.PAUSED -> infoLayout.textViewState.apply {
textAndVisible = resources.getString(R.string.state_paused)
drawableTop = ContextCompat.getDrawable(context, R.drawable.ic_action_pause)
}
null -> infoLayout.textViewState.isVisible = false
}
if (manga.source == MangaSource.LOCAL) {
infoLayout.textViewSource.isVisible = false

View File

@@ -53,6 +53,7 @@ import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
@@ -134,8 +135,14 @@ class DetailsViewModel @Inject constructor(
.map { it?.local }
.distinctUntilChanged()
.map { local ->
local?.file?.computeSize() ?: 0L
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(), 0)
if (local != null) {
runCatchingCancellable {
local.file.computeSize()
}.getOrDefault(0L)
} else {
0L
}
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.WhileSubscribed(5000), 0L)
@Deprecated("")
val description = details

View File

@@ -160,7 +160,7 @@ class ExploreFragment :
override fun onRetryClick(error: Throwable) = Unit
override fun onEmptyActionClick() {
startActivity(SettingsActivity.newManageSourcesIntent(context ?: return))
startActivity(Intent(context ?: return, SourcesCatalogActivity::class.java))
}
private fun onOpenManga(manga: Manga) {

View File

@@ -21,7 +21,6 @@ import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
import org.koitharu.kotatsu.explore.domain.ExploreRepository
import org.koitharu.kotatsu.explore.ui.model.ExploreButtons
import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
@@ -30,7 +29,6 @@ import org.koitharu.kotatsu.list.ui.model.EmptyHint
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.TipModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
@@ -56,8 +54,6 @@ class ExploreViewModel @Inject constructor(
valueProducer = { isSuggestionsEnabled },
)
val sortOrder = MutableStateFlow(SourcesSortOrder.MANUAL) // TODO
val onOpenManga = MutableEventFlow<Manga>()
val onActionDone = MutableEventFlow<ReversibleAction>()
val onShowSuggestionsTip = MutableEventFlow<Unit>()
@@ -129,31 +125,25 @@ class ExploreViewModel @Inject constructor(
randomLoading: Boolean,
newSources: Set<MangaSource>,
): List<ListModel> {
val result = ArrayList<ListModel>(sources.size + 4)
val result = ArrayList<ListModel>(sources.size + 3)
result += ExploreButtons(randomLoading)
if (recommendation != null) {
result += ListHeader(R.string.suggestions)
result += RecommendationsItem(recommendation)
}
if (sources.isNotEmpty()) {
result += ListHeader(R.string.remote_sources, R.string.catalog)
if (newSources.isNotEmpty()) {
result += TipModel(
key = TIP_NEW_SOURCES,
title = R.string.new_sources_text,
text = R.string.new_sources_text,
icon = R.drawable.ic_explore_normal,
primaryButtonText = R.string.manage,
secondaryButtonText = R.string.discard,
)
}
result += ListHeader(
textRes = R.string.remote_sources,
buttonTextRes = R.string.catalog,
badge = if (newSources.isNotEmpty()) "" else null,
)
sources.mapTo(result) { MangaSourceItem(it, isGrid) }
} else {
result += EmptyHint(
icon = R.drawable.ic_empty_common,
textPrimary = R.string.no_manga_sources,
textSecondary = R.string.no_manga_sources_text,
actionStringRes = R.string.manage,
actionStringRes = R.string.catalog,
)
}
return result

View File

@@ -8,6 +8,7 @@ import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.getSummary
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
@@ -112,7 +113,7 @@ fun exploreSourceListItemAD(
binding.root.setOnContextClickListenerCompat(eventListener)
bind {
binding.textViewTitle.text = item.source.title
binding.textViewTitle.text = item.source.getTitle(context)
binding.textViewSubtitle.text = item.source.getSummary(context)
val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)
binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run {
@@ -147,7 +148,7 @@ fun exploreSourceGridItemAD(
binding.root.setOnContextClickListenerCompat(eventListener)
bind {
binding.textViewTitle.text = item.source.title
binding.textViewTitle.text = item.source.getTitle(context)
val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Large, item.source.name)
binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run {
fallback(fallbackIcon)

View File

@@ -14,22 +14,37 @@ import com.google.android.material.R as materialR
@CheckResult
fun View.bindBadge(badge: BadgeDrawable?, counter: Int): BadgeDrawable? {
return if (counter > 0) {
val badgeDrawable = badge ?: initBadge(this)
badgeDrawable.number = counter
badgeDrawable.isVisible = true
badgeDrawable.align(this)
badgeDrawable
} else {
badge?.isVisible = false
badge
}
return bindBadgeImpl(badge, null, counter)
}
@CheckResult
fun View.bindBadge(badge: BadgeDrawable?, text: String?): BadgeDrawable? {
return bindBadgeImpl(badge, text, 0)
}
fun View.clearBadge(badge: BadgeDrawable?) {
BadgeUtils.detachBadgeDrawable(badge, this)
}
private fun View.bindBadgeImpl(
badge: BadgeDrawable?,
text: String?,
counter: Int,
): BadgeDrawable? = if (text != null || counter > 0) {
val badgeDrawable = badge ?: initBadge(this)
if (counter > 0) {
badgeDrawable.number = counter
} else {
badgeDrawable.text = text?.takeUnless { it.isEmpty() }
}
badgeDrawable.isVisible = true
badgeDrawable.align(this)
badgeDrawable
} else {
badge?.isVisible = false
badge
}
private fun initBadge(anchor: View): BadgeDrawable {
val badge = BadgeDrawable.create(anchor.context)
val resources = anchor.resources

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.list.ui.adapter
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import com.google.android.material.badge.BadgeDrawable
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.databinding.ItemHeaderButtonBinding
import org.koitharu.kotatsu.list.ui.model.ListHeader
@@ -12,6 +13,8 @@ fun listHeaderAD(
) = adapterDelegateViewBinding<ListHeader, ListModel, ItemHeaderButtonBinding>(
{ inflater, parent -> ItemHeaderButtonBinding.inflate(inflater, parent, false) },
) {
var badge: BadgeDrawable? = null
if (listener != null) {
binding.buttonMore.setOnClickListener {
listener.onListHeaderClick(item, it)
@@ -23,9 +26,11 @@ fun listHeaderAD(
if (item.buttonTextRes == 0) {
binding.buttonMore.isInvisible = true
binding.buttonMore.text = null
binding.buttonMore.clearBadge(badge)
} else {
binding.buttonMore.setText(item.buttonTextRes)
binding.buttonMore.isVisible = true
badge = itemView.bindBadge(badge, item.badge)
}
}
}

View File

@@ -6,38 +6,41 @@ import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
@Suppress("DataClassPrivateConstructor")
data class ListHeader private constructor(
private val text: CharSequence?,
@StringRes private val textRes: Int,
private val dateTimeAgo: DateTimeAgo?,
private val textRaw: Any,
@StringRes val buttonTextRes: Int,
val payload: Any?,
val badge: String?,
) : ListModel {
constructor(
text: CharSequence,
@StringRes buttonTextRes: Int = 0,
payload: Any? = null,
) : this(text, 0, null, buttonTextRes, payload)
badge: String? = null,
) : this(textRaw = text, buttonTextRes, payload, badge)
constructor(
@StringRes textRes: Int,
@StringRes buttonTextRes: Int = 0,
payload: Any? = null,
) : this(null, textRes, null, buttonTextRes, payload)
badge: String? = null,
) : this(textRaw = textRes, buttonTextRes, payload, badge)
constructor(
dateTimeAgo: DateTimeAgo,
@StringRes buttonTextRes: Int = 0,
payload: Any? = null,
) : this(null, 0, dateTimeAgo, buttonTextRes, payload)
badge: String? = null,
) : this(textRaw = dateTimeAgo, buttonTextRes, payload, badge)
fun getText(context: Context): CharSequence? = when {
text != null -> text
textRes != 0 -> context.getString(textRes)
else -> dateTimeAgo?.format(context.resources)
fun getText(context: Context): CharSequence? = when (textRaw) {
is CharSequence -> textRaw
is Int -> if (textRaw != 0) context.getString(textRaw) else null
is DateTimeAgo -> textRaw.format(context.resources)
else -> null
}
override fun areItemsTheSame(other: ListModel): Boolean {
return other is ListHeader && text == other.text && dateTimeAgo == other.dateTimeAgo && textRes == other.textRes
return other is ListHeader && textRaw == other.textRaw
}
}

View File

@@ -125,7 +125,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.container, null))
viewModel.isLoading.observe(this, this::onLoadingStateChanged)
viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
viewModel.counters.observe(this, ::onCountersChanged)
viewModel.feedCounter.observe(this, ::onFeedCounterChanged)
viewModel.appUpdate.observe(this, MenuInvalidator(this))
viewModel.onFirstStart.observeEvent(this) {
WelcomeSheet.show(supportFragmentManager)
@@ -278,10 +278,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
startActivity(IntentBuilder(this).manga(manga).build(), options)
}
private fun onCountersChanged(counters: Map<NavItem, Int>) {
counters.forEach { (navItem, counter) ->
navigationDelegate.setCounter(navItem, counter)
}
private fun onFeedCounterChanged(counter: Int) {
navigationDelegate.setCounter(NavItem.FEED, counter)
}
private fun onIncognitoModeChanged(isIncognito: Boolean) {

View File

@@ -4,15 +4,11 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.github.AppUpdateRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.NavItem
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
@@ -22,7 +18,6 @@ import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.main.domain.ReadingResumeEnabledUseCase
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import java.util.EnumMap
import javax.inject.Inject
@HiltViewModel
@@ -52,19 +47,8 @@ class MainViewModel @Inject constructor(
val appUpdate = appUpdateRepository.observeAvailableUpdate()
val counters = combine(
trackingRepository.observeUpdatedMangaCount(),
observeNewSourcesCount(),
) { tracks, newSources ->
val em = EnumMap<NavItem, Int>(NavItem::class.java)
em[NavItem.EXPLORE] = newSources
em[NavItem.FEED] = tracks
em
}.stateIn(
scope = viewModelScope + Dispatchers.Default,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyMap<NavItem, Int>(),
)
val feedCounter = trackingRepository.observeUpdatedMangaCount()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, 0)
init {
launchJob {
@@ -87,8 +71,4 @@ class MainViewModel @Inject constructor(
fun setIncognitoMode(isEnabled: Boolean) {
settings.isIncognitoModeEnabled = isEnabled
}
private fun observeNewSourcesCount() = sourcesRepository.observeNewSources()
.map { if (sourcesRepository.isSetupRequired()) 0 else it.size }
.distinctUntilChanged()
}

View File

@@ -1,11 +1,11 @@
package org.koitharu.kotatsu.search.ui.suggestion.adapter
import androidx.core.text.buildSpannedString
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.getSummary
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
import org.koitharu.kotatsu.core.util.ext.enqueueWith
@@ -14,7 +14,6 @@ import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemSearchSuggestionSourceBinding
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
import org.koitharu.kotatsu.settings.sources.adapter.appendNsfwLabel
fun searchSuggestionSourceAD(
coil: ImageLoader,
@@ -32,15 +31,7 @@ fun searchSuggestionSourceAD(
}
bind {
binding.textViewTitle.text = if (item.isNsfw) {
buildSpannedString {
append(item.source.title)
append(' ')
appendNsfwLabel(context)
}
} else {
item.source.title
}
binding.textViewTitle.text = item.source.getTitle(context)
binding.textViewSubtitle.text = item.source.getSummary(context)
binding.switchLocal.isChecked = item.isEnabled
val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)

View File

@@ -1,16 +1,8 @@
package org.koitharu.kotatsu.settings.sources.adapter
import android.content.Context
import android.graphics.Color
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.text.style.RelativeSizeSpan
import android.text.style.SuperscriptSpan
import android.view.View
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
@@ -19,12 +11,12 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.getSummary
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
import org.koitharu.kotatsu.core.ui.list.OnTipCloseListener
import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemSourceConfigBinding
@@ -51,15 +43,7 @@ fun sourceConfigItemCheckableDelegate(
}
bind {
binding.textViewTitle.text = if (item.isNsfw) {
buildSpannedString {
append(item.source.title)
append(' ')
appendNsfwLabel(context)
}
} else {
item.source.title
}
binding.textViewTitle.text = item.source.getTitle(context)
binding.switchToggle.isChecked = item.isEnabled
binding.switchToggle.isEnabled = item.isAvailable
binding.textViewDescription.text = item.source.getSummary(context)
@@ -101,15 +85,7 @@ fun sourceConfigItemDelegate2(
binding.imageViewMenu.setOnClickListener(eventListener)
bind {
binding.textViewTitle.text = if (item.isNsfw) {
buildSpannedString {
append(item.source.title)
append(' ')
appendNsfwLabel(context)
}
} else {
item.source.title
}
binding.textViewTitle.text = item.source.getTitle(context)
binding.imageViewAdd.isGone = item.isEnabled || !item.isAvailable
binding.imageViewRemove.isVisible = item.isEnabled
binding.imageViewMenu.isVisible = item.isEnabled
@@ -147,19 +123,6 @@ fun sourceConfigEmptySearchDelegate() =
R.layout.item_sources_empty,
) { }
fun SpannableStringBuilder.appendNsfwLabel(context: Context) = inSpans(
ForegroundColorSpan(
context.getThemeColor(
com.google.android.material.R.attr.colorError,
Color.RED,
),
),
RelativeSizeSpan(0.74f),
SuperscriptSpan(),
) {
append(context.getString(R.string.nsfw))
}
private fun showSourceMenu(
anchor: View,
item: SourceConfigItem.SourceItem,

View File

@@ -1,24 +1,27 @@
package org.koitharu.kotatsu.settings.sources.catalog
import androidx.core.text.buildSpannedString
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier.Companion.ignoreCaptchaErrors
import org.koitharu.kotatsu.core.model.getSummary
import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemCatalogPageBinding
import org.koitharu.kotatsu.databinding.ItemEmptyHintBinding
import org.koitharu.kotatsu.databinding.ItemSourceCatalogBinding
import org.koitharu.kotatsu.settings.sources.adapter.appendNsfwLabel
fun sourceCatalogItemSourceAD(
coil: ImageLoader,
@@ -35,15 +38,7 @@ fun sourceCatalogItemSourceAD(
}
bind {
binding.textViewTitle.text = if (item.source.isNsfw()) {
buildSpannedString {
append(item.source.title)
append(' ')
appendNsfwLabel(context)
}
} else {
item.source.title
}
binding.textViewTitle.text = item.source.getTitle(context)
if (item.showSummary) {
binding.textViewDescription.text = item.source.getSummary(context)
binding.textViewDescription.isVisible = true
@@ -57,6 +52,7 @@ fun sourceCatalogItemSourceAD(
placeholder(fallbackIcon)
fallback(fallbackIcon)
source(item.source)
ignoreCaptchaErrors()
enqueueWith(coil)
}
}
@@ -77,3 +73,30 @@ fun sourceCatalogItemHintAD(
binding.textSecondary.setTextAndVisible(item.text)
}
}
fun sourceCatalogPageAD(
listener: OnListItemClickListener<SourceCatalogItem.Source>,
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
) = adapterDelegateViewBinding<SourceCatalogPage, SourceCatalogPage, ItemCatalogPageBinding>(
{ inflater, parent -> ItemCatalogPageBinding.inflate(inflater, parent, false) },
) {
val sourcesAdapter = SourcesCatalogAdapter(listener, coil, lifecycleOwner)
with(binding.recyclerView) {
setHasFixedSize(true)
adapter = sourcesAdapter
}
val insetsDelegate = WindowInsetsDelegate()
ViewCompat.setOnApplyWindowInsetsListener(itemView, insetsDelegate)
itemView.addOnLayoutChangeListener(insetsDelegate)
insetsDelegate.addInsetsListener { insets ->
binding.recyclerView.updatePadding(
bottom = insets.bottom + binding.recyclerView.paddingTop,
)
}
bind {
sourcesAdapter.items = item.items
}
}

View File

@@ -0,0 +1,19 @@
package org.koitharu.kotatsu.settings.sources.catalog
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.ContentType
data class SourceCatalogPage(
val type: ContentType,
val items: List<SourceCatalogItem>,
) : ListModel {
override fun areItemsTheSame(other: ListModel): Boolean {
return other is SourceCatalogPage && other.type == type
}
override fun getChangePayload(previousState: ListModel): Any? {
return ListModelDiffCallback.PAYLOAD_NESTED_LIST_CHANGED
}
}

View File

@@ -10,30 +10,32 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import coil.ImageLoader
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.tabs.TabLayout
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
import org.koitharu.kotatsu.core.util.ext.getLocaleDisplayName
import org.koitharu.kotatsu.core.util.ext.getDisplayName
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.toLocale
import org.koitharu.kotatsu.databinding.ActivitySourcesCatalogBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
import javax.inject.Inject
@AndroidEntryPoint
class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
TabLayout.OnTabSelectedListener,
OnListItemClickListener<SourceCatalogItem.Source>,
AppBarOwner, MenuItem.OnActionExpandListener {
@Inject
lateinit var coil: ImageLoader
private var newSourcesSnackbar: Snackbar? = null
override val appBar: AppBarLayout
get() = viewBinding.appbar
@@ -43,19 +45,18 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
super.onCreate(savedInstanceState)
setContentView(ActivitySourcesCatalogBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
initTabs()
val sourcesAdapter = SourcesCatalogAdapter(this, coil, this)
with(viewBinding.recyclerView) {
setHasFixedSize(true)
adapter = sourcesAdapter
}
viewModel.content.observe(this, sourcesAdapter)
val pagerAdapter = SourcesCatalogPagerAdapter(this, coil, this)
viewBinding.pager.adapter = pagerAdapter
val tabMediator = TabLayoutMediator(viewBinding.tabs, viewBinding.pager, pagerAdapter)
tabMediator.attach()
viewModel.content.observe(this, pagerAdapter)
viewModel.hasNewSources.observe(this, ::onHasNewSourcesChanged)
viewModel.onActionDone.observeEvent(
this,
ReversibleActionObserver(viewBinding.recyclerView),
ReversibleActionObserver(viewBinding.pager),
)
viewModel.locale.observe(this) {
supportActionBar?.subtitle = it.getLocaleDisplayName(this)
supportActionBar?.subtitle = it?.toLocale().getDisplayName(this)
}
addMenuProvider(SourcesCatalogMenuProvider(this, viewModel, this))
}
@@ -65,27 +66,15 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
left = insets.left,
right = insets.right,
)
viewBinding.recyclerView.updatePadding(
bottom = insets.bottom + viewBinding.recyclerView.paddingTop,
)
}
override fun onItemClick(item: SourceCatalogItem.Source, view: View) {
viewModel.addSource(item.source)
}
override fun onTabSelected(tab: TabLayout.Tab) {
viewModel.setContentType(tab.tag as ContentType)
}
override fun onTabUnselected(tab: TabLayout.Tab) = Unit
override fun onTabReselected(tab: TabLayout.Tab) {
viewBinding.recyclerView.firstVisibleItemPosition = 0
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
viewBinding.tabs.isVisible = false
viewBinding.pager.isUserInputEnabled = false
val sq = (item.actionView as? SearchView)?.query?.trim()?.toString().orEmpty()
viewModel.performSearch(sq)
return true
@@ -93,21 +82,35 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
viewBinding.tabs.isVisible = true
viewBinding.pager.isUserInputEnabled = true
viewModel.performSearch(null)
return true
}
private fun initTabs() {
val tabs = viewBinding.tabs
for (type in ContentType.entries) {
if (viewModel.isNsfwDisabled && type == ContentType.HENTAI) {
continue
private fun onHasNewSourcesChanged(hasNewSources: Boolean) {
if (hasNewSources) {
if (newSourcesSnackbar?.isShownOrQueued == true) {
return
}
val tab = tabs.newTab()
tab.setText(type.titleResId)
tab.tag = type
tabs.addTab(tab)
val snackbar = Snackbar.make(viewBinding.pager, R.string.new_sources_text, Snackbar.LENGTH_INDEFINITE)
snackbar.setAction(R.string.explore) {
NewSourcesDialogFragment.show(supportFragmentManager)
}
snackbar.addCallback(
object : Snackbar.Callback() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
if (event == DISMISS_EVENT_SWIPE) {
viewModel.skipNewSources()
}
}
},
)
snackbar.show()
newSourcesSnackbar = snackbar
} else {
newSourcesSnackbar?.dismiss()
newSourcesSnackbar = null
}
tabs.addOnTabSelectedListener(this)
}
}

View File

@@ -9,7 +9,9 @@ import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.getLocaleDisplayName
import org.koitharu.kotatsu.core.util.LocaleComparator
import org.koitharu.kotatsu.core.util.ext.getDisplayName
import org.koitharu.kotatsu.core.util.ext.toLocale
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
class SourcesCatalogMenuProvider(
@@ -57,15 +59,17 @@ class SourcesCatalogMenuProvider(
}
private fun showLocalesMenu() {
val locales = viewModel.locales.map {
it to it.getLocaleDisplayName(activity)
val locales = viewModel.locales.mapTo(ArrayList(viewModel.locales.size)) {
it to it?.toLocale()
}
locales.sortWith(compareBy(nullsFirst(LocaleComparator())) { it.second })
val anchor: View = (activity as AppBarOwner).appBar.let {
it.findViewById<View?>(R.id.toolbar) ?: it
}
val menu = PopupMenu(activity, anchor)
for ((i, lc) in locales.withIndex()) {
menu.menu.add(Menu.NONE, Menu.NONE, i, lc.second)
menu.menu.add(Menu.NONE, Menu.NONE, i, lc.second.getDisplayName(activity))
}
menu.setOnMenuItemClickListener {
viewModel.setLocale(locales.getOrNull(it.order)?.first)

View File

@@ -0,0 +1,25 @@
package org.koitharu.kotatsu.settings.sources.catalog
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import org.koitharu.kotatsu.core.model.titleResId
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
class SourcesCatalogPagerAdapter(
listener: OnListItemClickListener<SourceCatalogItem.Source>,
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
) : BaseListAdapter<SourceCatalogPage>(), TabLayoutMediator.TabConfigurationStrategy {
init {
delegatesManager.addDelegate(sourceCatalogPageAD(listener, coil, lifecycleOwner))
}
override fun onConfigureTab(tab: TabLayout.Tab, position: Int) {
val item = items.getOrNull(position) ?: return
tab.setText(item.type.titleResId)
}
}

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.settings.sources.catalog
import androidx.annotation.MainThread
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.internal.lifecycle.RetainedLifecycleImpl
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -8,9 +9,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel
@@ -21,6 +23,8 @@ import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mapToSet
import java.util.EnumMap
import java.util.EnumSet
import java.util.Locale
import javax.inject.Inject
@@ -28,30 +32,27 @@ import javax.inject.Inject
class SourcesCatalogViewModel @Inject constructor(
private val repository: MangaSourcesRepository,
private val listProducerFactory: SourcesCatalogListProducer.Factory,
settings: AppSettings,
private val settings: AppSettings,
) : BaseViewModel() {
private val lifecycle = RetainedLifecycleImpl()
private var searchQuery: String? = null
val onActionDone = MutableEventFlow<ReversibleAction>()
val contentType = MutableStateFlow(ContentType.entries.first())
val locales = repository.allMangaSources.mapToSet { it.locale }
val locale = MutableStateFlow(Locale.getDefault().language.takeIf { it in locales })
val isNsfwDisabled = settings.isNsfwContentDisabled
val hasNewSources = repository.observeNewSources()
.map { it.isNotEmpty() }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)
private val listProducer: StateFlow<SourcesCatalogListProducer?> = combine(
locale,
contentType,
) { lc, type ->
listProducerFactory.create(lc, type, lifecycle).also {
it.setQuery(searchQuery)
}
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
private val listProducers = locale.map { lc ->
createListProducers(lc)
}.stateIn(viewModelScope, SharingStarted.Eagerly, createListProducers(locale.value))
val content = listProducer.flatMapLatest {
it?.list ?: emptyFlow()
}.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
val content: StateFlow<List<SourceCatalogPage>> = listProducers.flatMapLatest {
val flows = it.entries.map { (type, producer) -> producer.list.map { x -> SourceCatalogPage(type, x) } }
combine<SourceCatalogPage, List<SourceCatalogPage>>(flows, Array<SourceCatalogPage>::toList)
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
override fun onCleared() {
super.onCleared()
@@ -60,21 +61,36 @@ class SourcesCatalogViewModel @Inject constructor(
fun performSearch(query: String?) {
searchQuery = query
listProducer.value?.setQuery(query)
listProducers.value.forEach { (_, v) -> v.setQuery(query) }
}
fun setLocale(value: String?) {
locale.value = value
}
fun setContentType(value: ContentType) {
contentType.value = value
}
fun addSource(source: MangaSource) {
launchJob(Dispatchers.Default) {
val rollback = repository.setSourceEnabled(source, true)
onActionDone.call(ReversibleAction(R.string.source_enabled, rollback))
}
}
fun skipNewSources() {
launchJob {
repository.assimilateNewSources()
}
}
@MainThread
private fun createListProducers(lc: String?): Map<ContentType, SourcesCatalogListProducer> {
val types = EnumSet.allOf(ContentType::class.java)
if (settings.isNsfwContentDisabled) {
types.remove(ContentType.HENTAI)
}
return types.associateWithTo(EnumMap(ContentType::class.java)) { type ->
listProducerFactory.create(lc, type, lifecycle).also {
it.setQuery(searchQuery)
}
}
}
}

View File

@@ -11,7 +11,8 @@
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
android:fitsSystemWindows="true"
app:liftOnScroll="false">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
@@ -27,21 +28,10 @@
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/layout_list"
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_source_config" />
</FrameLayout>
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_list"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.koitharu.kotatsu.core.ui.list.fastscroll.FastScrollRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_source_config" />
</FrameLayout>

View File

@@ -539,4 +539,8 @@
<string name="color_correction_apply_text">Гэтыя налады могуць прымяняцца глабальна або толькі да бягучай мангі. Пры глабальным прымяненні індывідуальныя налады не будуць перавызначаны.</string>
<string name="grayscale">Адценні шэрага</string>
<string name="disable_battery_optimization_summary_downloads">Можа дапамагчы з пачаткам загрузкі, калі ў вас узнікаюць з ёй праблемы</string>
<string name="welcome_text">Выберыце, якія крыніцы змесціва вы хочаце ўключыць. Гэта таксама можна наладзіць пазней у наладах</string>
<string name="restore">Аднавіць</string>
<string name="backup_date_">Дата стварэння рэзервовай копіі: %s</string>
<string name="sync_auth">Увайдзіце, каб сінхранізаваць уліковы запіс</string>
</resources>

View File

@@ -268,7 +268,7 @@
<string name="show_reading_indicators">Mostrare gli indicatori di progresso della lettura</string>
<string name="data_deletion">Eliminazione dei dati</string>
<string name="show_reading_indicators_summary">Mostra la percentuale di lettura nella cronologia e nei preferiti</string>
<string name="exclude_nsfw_from_history_summary">I manga contrassegnati come per adulti non verranno mai aggiunti alla cronologia e i vostri progressi non verranno salvati</string>
<string name="exclude_nsfw_from_history_summary">I manga contrassegnati come per adulti non verranno mai aggiunti alla cronologia e i progressi non saranno salvati</string>
<string name="clear_cookies_summary">Può aiutare in caso di problemi. Tutte le autorizzazioni saranno invalidate</string>
<string name="show_all">Mostra tutto</string>
<string name="logout">Esci</string>
@@ -345,12 +345,12 @@
\n Tutti i manga in esso contenuti andranno persi e questo non può essere annullato.</string>
<string name="history_shortcuts_summary">Rendere disponibili i manga recenti premendo a lungo sull\'icona dell\'applicazione</string>
<string name="reader_info_bar">Mostra la barra delle informazioni nel lettore</string>
<string name="manga_error_description_pattern">Dettagli dell\'errore:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;1. Prova ad &lt;a href=%2$s&gt;aprire il manga in un browser web&lt;/a&gt; per assicurarsi che sia disponibile sulla sua fonte&lt;br&gt;2. Se è disponibile, inviare una segnalazione di errore agli sviluppatori.</string>
<string name="manga_error_description_pattern">Dettagli dell\'errore:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Prova ad &lt;a href=%2$s&gt;aprire il manga in un browser web&lt;/a&gt; per assicurarsi che sia disponibile sulla sua fonte&lt;br&gt;2. Controllare di stare usando la &lt;a href=kotatsu://about&gt;versione più recente di Kotatsu&lt;/a&gt;&lt;br&gt;3. Se è disponibile, inviare una segnalazione di errore agli sviluppatori.</string>
<string name="text_unsaved_changes_prompt">Salvare o eliminare le modifiche non salvate\?</string>
<string name="discard">Abbandona</string>
<string name="language">Lingua</string>
<string name="share_logs">Condividi i registri</string>
<string name="enable_logging_summary">Registra alcune azioni a scopo di debug</string>
<string name="enable_logging_summary">Registra alcune azioni a scopo di debug. Non attivare se non si è sicuri di cosa si stia facendo</string>
<string name="enable_logging">Abilita la registrazione</string>
<string name="show_suspicious_content">Mostra il contenuto sospetto</string>
<string name="theme_name_dynamic">Dinamico</string>
@@ -366,7 +366,181 @@
<string name="theme_name_rikka">Rikka</string>
<string name="theme_name_miku">Miku</string>
<string name="allow_unstable_updates">Permetti aggiornamenti instabili</string>
<string name="allow_unstable_updates_summary">Proponi aggiornamenti alle versioni beta dell\'app</string>
<string name="allow_unstable_updates_summary">Ricevi notifiche riguardo versioni instabili</string>
<string name="download_started">Scaricamento iniziato</string>
<string name="theme_name_sakura">Sakura</string>
<string name="languages">Lingue</string>
<string name="zoom_in">Ingrandire</string>
<string name="captcha_required_summary">%s richiede il completamento di un captcha per funzionare correttamente</string>
<string name="sources_catalog">Catalogo fonti</string>
<string name="download_option_all_unread">Tutti i capitoli non letti</string>
<string name="frequency_every_day">Ogni giorno</string>
<string name="categories">Categorie</string>
<string name="progress">Progresso</string>
<string name="cancel_all">Annulla tutti</string>
<string name="sync_host_description">Puoi usare un server di sincronizzazione self-hosted oppure uno di default. Non cambiare se non si è sicuri di cosa si stia facendo.</string>
<string name="error_corrupted_file">Dati non validi in ritorno o file corrotto</string>
<string name="pick_custom_directory">Seleziona cartella personalizzata</string>
<string name="list_options">Mostra lista opzioni</string>
<string name="related_manga_summary">Mostra una lista di manga correlati. In alcuni casi potrebbe essere imprecisa o mancante</string>
<string name="remove_completed_downloads_confirm">La tua cronologia dei download sarà permanentemente cancellata</string>
<string name="reader_zoom_buttons_summary">Se mostrare o no i tasti di controllo dello zoom in basso a destra</string>
<string name="content_type_manga">Manga</string>
<string name="tracker_wifi_only_summary">Non controllare nuovi capitoli mentre è in uso una connessione a consumo</string>
<string name="error_multiple_states_not_supported">Questa fonte di manga non supporta il filtraggio per multipli stati</string>
<string name="order_added">Aggiunto</string>
<string name="source_summary_pattern">%1$s, %2$s</string>
<string name="on_device">Su dispositivo</string>
<string name="password">Password</string>
<string name="download_option_whole_manga">L\'intero manga</string>
<string name="settings_apply_restart_required">Riavviare l\'applicazione per applicare queste modifiche</string>
<string name="backup_frequency">Frequenza creazione backup</string>
<string name="data_and_privacy">Dati e privacy</string>
<string name="content_type_hentai">Hentai</string>
<string name="clear_source_cookies_summary">Pulisci cookies solo per dominio specifico. Nella maggior parte dei casi invaliderà l\'autorizzazione</string>
<string name="downloads_wifi_only_summary">Smetti di scaricare quando si passa a rete mobile</string>
<string name="suggest_new_sources">Suggerisci nuove fonti dopo aggiornamento dell\'app</string>
<string name="user_agent">Intestazione UserAgent</string>
<string name="error_filter_states_genre_not_supported">Questa fonte non supporta il filtraggio per sia genere che stato</string>
<string name="ignore_ssl_errors">Ignora errori SSL</string>
<string name="error_filter_locale_genre_not_supported">Questa fonte non supporta il filtraggio per sia genere che localizzazione</string>
<string name="periodic_backups_enable">Abilita backup periodici</string>
<string name="server_address">Indirizzo server</string>
<string name="content_type_comics">Fumetti</string>
<string name="moved_to_top">Spostato in cima</string>
<string name="find_similar">Trova simili</string>
<string name="data_not_restored_text">Assicurati di aver selezionato il file di backup corretto</string>
<string name="catalog">Catalogo</string>
<string name="view_list">Vedi lista</string>
<string name="welcome_text">Selezionare le fonti di contenuti da abilitare. Si può configurare anche successivamente nelle impostazioni</string>
<string name="unknown">Sconosciuto</string>
<string name="in_progress">In progresso</string>
<string name="download_option_manual_selection">Selezione manuale capitoli</string>
<string name="enhanced_colors_summary">Riduce il banding, ma può impattare la performance</string>
<string name="pause">Pausa</string>
<string name="remove_completed">Rimuovi completati</string>
<string name="items_limit_exceeded">Impossibile aggiungere altri oggetti</string>
<string name="frequency_every_2_days">Ogni 2 giorni</string>
<string name="suggestions_notifications_summary">A volte mostra le notifiche sui manga suggeriti</string>
<string name="invalid_value_message">Valore non valido</string>
<string name="downloads_cancelled">I download sono stati annullati</string>
<string name="reader_optimize">Riduci consumo di memoria (beta)</string>
<string name="apply">Applica</string>
<string name="restore">Ripristina</string>
<string name="data_not_restored">Dati non ripristinati</string>
<string name="manage_sources">Gestisci fonti</string>
<string name="directories">Cartelle</string>
<string name="local_manga_directories">Cartelle manga locali</string>
<string name="manage_categories">Gestisci categorie</string>
<string name="no_manga_sources_found">Nessuna fonte manga trovata in base alla tua query</string>
<string name="color_light">Chiaro</string>
<string name="web_view_unavailable">WebView non disponibile: controlla se è installato un provider di WebView</string>
<string name="genres_search_hint">Inizia a scrivere il nome del genere</string>
<string name="port">Porta</string>
<string name="type">Tipo</string>
<string name="search_hint">Inserisci titolo manga, genere o nome fonte</string>
<string name="frequency_once_per_week">Una volta a settimana</string>
<string name="description">Descrizione</string>
<string name="periodic_backups">Backup periodici</string>
<string name="reader_zoom_buttons">Mostra tasti zoom</string>
<string name="sources_reorder_tip">Tocca e tieni premuto su un elemento per riordinarlo</string>
<string name="globally">Globalmente</string>
<string name="resume">Riprendi</string>
<string name="images_proxy_title">Proxy ottimizzazione immagini</string>
<string name="username">Nome utente</string>
<string name="frequency_twice_per_month">Due volte al mese</string>
<string name="main_screen_sections">Sezioni principali schermo</string>
<string name="advanced">Avanzate</string>
<string name="downloads_settings_info">Se stai avendo problemi di blocco dal server, puoi abilitare il rallentamento dei download per ogni fonte di manga nelle impostazioni delle fonti</string>
<string name="sync_settings">Impostazioni di sincronizzazione</string>
<string name="online_variant">Variante online</string>
<string name="disable_battery_optimization_summary_downloads">Potrebbe aiutare a far iniziare il download in caso di problemi</string>
<string name="error_multiple_genres_not_supported">Questa fonte di manga non supporta il filtraggio per multipli generi</string>
<string name="download_option_all_unread_b">Tutti i capitoli non letti (%s)</string>
<string name="authorization_optional">Autorizzazione (opzionale)</string>
<string name="color_dark">Scuro</string>
<string name="this_manga">Questo manga</string>
<string name="reader_info_bar_summary">Mostra l\'orario attuale e il progresso di lettura in cima allo schermo</string>
<string name="translations">Traduzioni</string>
<string name="downloads_paused">I download sono stati messi in pausa</string>
<string name="too_many_requests_message">Troppe richieste. Riprova più tardi</string>
<string name="downloads_wifi_only">Scarica solo via Wi-Fi</string>
<string name="lock_screen_rotation">Blocca rotazione schermo</string>
<string name="cancel_all_downloads_confirm">Tutti i download attivi saranno annullati, i dati scaricati parzialmente saranno persi</string>
<string name="by_relevance">Rilevanza</string>
<string name="related_manga">Manga correlati</string>
<string name="state_abandoned">Abbandonato</string>
<string name="download_option_first_n_chapters">Primo %s</string>
<string name="keep_screen_on">Tieni schermo acceso</string>
<string name="paused">In pausa</string>
<string name="text_downloads_list_holder">Non hai alcun download</string>
<string name="skip">Salta</string>
<string name="error_search_not_supported">Questa fonte di manga non supporta la ricerca</string>
<string name="invalid_port_number">Numero di porta non valido</string>
<string name="suggestions_wifi_only_summary">Non aggiornare suggerimenti mentre è in uso una connessione a consumo</string>
<string name="webtoon_zoom_summary">Consenti gesto di zoom in modalità webtoon</string>
<string name="frequency_once_per_month">Una volta al mese</string>
<string name="network">Rete</string>
<string name="downloaded">Scaricato</string>
<string name="suggestions_enable_prompt">Vuoi ricevere suggerimenti personalizzati di manga?</string>
<string name="manual">Manuale</string>
<string name="custom_directory">Cartella personalizzata</string>
<string name="more">Di più</string>
<string name="reader_optimize_summary">Riduci la qualità delle pagine fuori schermo per usare meno memoria</string>
<string name="address">Indirizzo</string>
<string name="color_correction_apply_text">Queste impostazioni possono essere applicate globalmente o per solo questo manga. Le applicazioni globali non sovrascrivono quelle individuali.</string>
<string name="source_enabled">Fonte abilitata</string>
<string name="enhanced_colors">Modalità colori 32-bit</string>
<string name="folder_with_images_import_description">Puoi selezionare una cartella contenente archivi o immagini. Ogni archivio (o sottocartella) sarà riconosciuto come singolo capitolo.</string>
<string name="default_section">Sezione default</string>
<string name="background">Sfondo</string>
<string name="disable_nsfw_summary">Disabilita fonti per adulti e nascondi manga per adulti dalla lista se possibile</string>
<string name="speed_value">x%.1f</string>
<string name="downloads_removed">I download sono stati rimossi</string>
<string name="pages_animation_summary">Animazioni cambio pagina</string>
<string name="no_access_to_file">Non hai accesso a questo file o cartella</string>
<string name="mirror_switching">Scegli mirror automaticamente</string>
<string name="restore_summary">Ripristina backup creato in precedenza</string>
<string name="show_pages_numbers_summary">Mostra numeri di pagina nell\'angolo inferiore</string>
<string name="zoom_out">Rimpicciolire</string>
<string name="keep_screen_on_summary">Non spegnere lo schermo durante la lettura di manga</string>
<string name="download_option_next_unread_n_chapters">Prossimo %s non letto</string>
<string name="clear_network_cache">Pulisci cache di rete</string>
<string name="voice_search">Ricerca vocale</string>
<string name="enable">Abilita</string>
<string name="backup_date_">Data del backup: %s</string>
<string name="images_procy_description">Usa il servizio wsrv.nl per ridurre i consumi di rete e velocizzare il caricamento delle immagini, se possibile</string>
<string name="no_manga_sources_catalog_text">Non sono disponibili fonti in questa sezione, o potrebbero essere state aggiunte tutte.
\nRimani sintonizzato</string>
<string name="available_d">Disponibile: %1$d</string>
<string name="manga_branch_title_template">%1$s (%2$s)</string>
<string name="state">Stato</string>
<string name="manga_list">Lista manga</string>
<string name="grayscale">Bianco e nero</string>
<string name="disable_nsfw">Disabilita contenuti per adulti</string>
<string name="last_successful_backup">Ultimo backup completato: %s</string>
<string name="color_white">Bianco</string>
<string name="downloads_resumed">I download sono stati ripresi</string>
<string name="details_button_tip">Tocca e tieni premuto il tasto Leggi per vedere più opzioni</string>
<string name="state_paused">In pausa</string>
<string name="to_top">Va in cima</string>
<string name="show">Mostra</string>
<string name="sync_auth_hint">Puoi accedere ad un account esistente o crearne uno nuovo</string>
<string name="backups_output_directory">Cartella salvataggio backup</string>
<string name="invert_colors">Inverti colori</string>
<string name="mirror_switching_summary">Cambiare dominio di fonte del manga automaticamente in caso di errore se sono disponibili mirror</string>
<string name="no_thanks">No grazie</string>
<string name="suggest_new_sources_summary">Chiedi se aggiungere nuove fonti dopo aggiornamento dell\'applicazione</string>
<string name="speed">Velocità</string>
<string name="download_option_all_chapters">Tutti i capitoli con traduzioni %s</string>
<string name="content_type_other">Altro</string>
<string name="suggestion_manga">Consigliato: %s</string>
<string name="color_black">Nero</string>
<string name="this_month">Questo mese</string>
<string name="sync_auth">Accedi per sincronizzare l\'account</string>
<string name="proxy">Proxy</string>
<string name="restore_backup_description">Importa un backup di dati utente creato precedentemente</string>
<string name="got_it">Ho capito</string>
<string name="comics_archive_import_description">Puoi selezionare uno o più file .cbz o .zip, ogni file sarà riconosciuto come un manga a parte.</string>
<string name="show_on_shelf">Mostra sullo Scaffale</string>
</resources>

View File

@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="read">Leitura</string>
<string name="read">Iniciar</string>
<string name="you_have_not_favourites_yet">Ainda não há favoritos</string>
<string name="local_storage">Armazenamento local</string>
<string name="favourites">Favoritos</string>
<string name="history">Histórico</string>
<string name="error_occurred">Ocorreu um erro</string>
<string name="network_error">Erro de rede</string>
<string name="details">Detalhes</string>
<string name="details">Detalhado</string>
<string name="chapters">Capítulos</string>
<string name="list">Lista</string>
<string name="detailed_list">Lista detalhada</string>
@@ -104,7 +104,7 @@
<string name="favourites_category_empty">Categoria vazia</string>
<string name="read_later">Leia mais tarde</string>
<string name="updates">Atualizações</string>
<string name="search_results">Procurar resultados</string>
<string name="search_results">Resultados encontrados</string>
<string name="new_version_s">Nova versão: %s</string>
<string name="size_s">Tamanho: %s</string>
<string name="clear_updates_feed">Limpar feed de atualizações</string>
@@ -128,7 +128,7 @@
<string name="tap_to_try_again">Toque para tentar novamente</string>
<string name="reader_mode_hint">A configuração escolhida será lembrada para este mangá</string>
<string name="silent">Silencioso</string>
<string name="captcha_required">CAPTCHA obrigatório</string>
<string name="captcha_required">CAPTCHA Obrigatório</string>
<string name="captcha_solve">Resolver</string>
<string name="clear_cookies">Limpar cookies</string>
<string name="cookies_cleared">Todos os cookies foram removidos</string>
@@ -201,7 +201,7 @@
<string name="black_dark_theme_summary">Consome menos bateria em telas AMOLED</string>
<string name="create_backup">Criar backup de dados</string>
<string name="text_local_holder_secondary">Salve-o de fontes online ou importe arquivos.</string>
<string name="check_for_updates">Verifique se há atualizações</string>
<string name="check_for_updates">Verificar se há atualizações</string>
<string name="text_feed_holder">Novos capítulos do que você está lendo são exibidos aqui</string>
<string name="app_version">Versão %s</string>
<string name="zoom_mode_fit_width">Ajustar à largura</string>
@@ -288,7 +288,7 @@
<string name="history_cleared">Histórico apagado</string>
<string name="manage">Gerenciar</string>
<string name="no_bookmarks_yet">Sem páginas marcadas ainda</string>
<string name="no_bookmarks_summary">Você pode criar um marcador de página enquanto lê o mangá</string>
<string name="no_bookmarks_summary">Você pode marcar uma página enquanto lê o mangá</string>
<string name="bookmarks_removed">Marcadores de página removidos</string>
<string name="no_manga_sources">Sem fontes de mangás</string>
<string name="select_range">Selecionar intervalo</string>
@@ -339,7 +339,7 @@
<string name="services">Serviços</string>
<string name="nothing_here">Não há nada aqui</string>
<string name="server_error">Erro do lado do servidor (%1$d). Por favor, tente novamente mais tarde</string>
<string name="compact">Compactar</string>
<string name="compact">Compacto</string>
<string name="enable_logging">Habilitar registros</string>
<string name="share_logs">Compartilhar registros</string>
<string name="error_no_space_left">Não há espaço disponível no dispositivo</string>
@@ -416,7 +416,7 @@
<string name="manga_branch_title_template">%1$s (%2$s)</string>
<string name="download_option_all_chapters">Todos os capítulos com tradução %s</string>
<string name="download_option_whole_manga">O mangá inteiro</string>
<string name="download_option_first_n_chapters">Primeiro %s</string>
<string name="download_option_first_n_chapters">Primeiros %s</string>
<string name="download_option_next_unread_n_chapters">Próximos %s não lidos</string>
<string name="download_option_all_unread">Todos os capítulos não lidos</string>
<string name="download_option_all_unread_b">Todos os capítulos não lidos (%s)</string>
@@ -445,7 +445,7 @@
<string name="local_manga_directories">Diretórios locais de mangá</string>
<string name="languages">Idiomas</string>
<string name="zoom_in">Aumentar o zoom</string>
<string name="captcha_required_summary">%s requer que um captcha seja resolvido para funcionar</string>
<string name="captcha_required_summary">%s requer que o captcha seja resolvido para funcionar corretamente</string>
<string name="progress">Progresso</string>
<string name="error_corrupted_file">Dados inválidos ou o arquivo está corrompido</string>
<string name="related_manga_summary">Mostra uma lista de mangás relacionados. Em alguns casos, ela pode estar incorreta ou ausente</string>

View File

@@ -542,5 +542,5 @@
<string name="sync_auth">Войти в аккаунт синхронизации</string>
<string name="welcome_text">Пожалуйста, выберите, какие источники вы хотите включить. Это можно изменить в настройках позже</string>
<string name="restore">Восстановить</string>
<string name="backup_date_">Дата резервного копирования: %s</string>
<string name="backup_date_">Дата создания резервной копии: %s</string>
</resources>

View File

@@ -12,12 +12,12 @@
<string name="grid">Табла</string>
<string name="list_mode">Режим листе</string>
<string name="settings">Подешавања</string>
<string name="remote_sources">Манга извори</string>
<string name="remote_sources">Извори манге</string>
<string name="loading_">Учитавање…</string>
<string name="chapter_d_of_d">Поглавље %1$d од %2$d</string>
<string name="close">Затвори</string>
<string name="try_again">Покушај поново</string>
<string name="clear_history">Очисти историју</string>
<string name="clear_history">Избриши историју</string>
<string name="nothing_found">Ништа није пронађено</string>
<string name="history_is_empty">Још нема историје</string>
<string name="read">Читај</string>
@@ -29,23 +29,23 @@
<string name="create_shortcut">Направи пречицу…</string>
<string name="invalid_port_number">Неважећи број порта</string>
<string name="search_manga">Претражи мангу</string>
<string name="by_name">Име</string>
<string name="clear_pages_cache">Обришите кеш странице</string>
<string name="by_name">Имену</string>
<string name="clear_pages_cache">Избриши кеш странице</string>
<string name="manga_downloading_">Преузимање…</string>
<string name="switch_pages">Окретање страница</string>
<string name="switch_pages">Листање страница</string>
<string name="delete_manga">Избриши мангу</string>
<string name="search_on_s">Претрага по %s</string>
<string name="internal_storage">Интерна меморија</string>
<string name="internal_storage">Унутрашња меморија</string>
<string name="external_storage">Спољна меморија</string>
<string name="language">Језик</string>
<string name="allow_unstable_updates">Дозволите нестабилна ажурирања</string>
<string name="allow_unstable_updates">Дозволи нестабилна ажурирања</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
<string name="standard">Стандардни</string>
<string name="webtoon">Манхва</string>
<string name="read_mode">Режим читања</string>
<string name="grid_size">Величина мреже</string>
<string name="clear_thumbs_cache">Обришите кеш сличица</string>
<string name="clear_search_history">Обришите историју претраге</string>
<string name="clear_thumbs_cache">Избриши кеш сличица</string>
<string name="clear_search_history">Избриши историју претраге</string>
<string name="gestures_only">Само покрети</string>
<string name="app_update_available">Доступна је нова верзија апликације</string>
<string name="open_in_browser">Отворите у веб прегледачу</string>
@@ -57,21 +57,21 @@
<string name="app_version">Верзија %s</string>
<string name="black_dark_theme">Црна</string>
<string name="black_dark_theme_summary">Троши мање енергије на AMOLED екранима</string>
<string name="show_pages_numbers">Нумерисање страница</string>
<string name="preload_pages">Претходно учитавање страница</string>
<string name="show_pages_numbers">Приказ бројева на страницама</string>
<string name="preload_pages">Преучитавање страница</string>
<string name="appearance">Изглед</string>
<string name="check_new_chapters_title">Проверите да ли постоје нова поглавља и обавестите их</string>
<string name="show_notification_new_chapters_on">Добићете обавештења о ажурирањима манге коју читате</string>
<string name="show_notification_new_chapters_on">Добићеш обавештења о ажурирањима манге коју читаш</string>
<string name="default_mode">Подразумевани режим</string>
<string name="network_unavailable">Мрежа није доступна</string>
<string name="network_unavailable_hint">Укључите Wi-Fi или мобилну мрежу да бисте читали мангу на мрежи</string>
<string name="allow_unstable_updates_summary">Понуди ажурирања за бета верзије апликације</string>
<string name="allow_unstable_updates_summary">Добијај обавештења о нестабилним верзијама апликације</string>
<string name="translations">Преводи</string>
<string name="data_and_privacy">Подаци и приватност</string>
<string name="network">Мрежа</string>
<string name="show_pages_numbers_summary">Прикажите бројеве страница у доњем углу</string>
<string name="pages_animation_summary">Анимација окретања страница</string>
<string name="images_proxy_title">Проки сервер за оптимизацију слика</string>
<string name="images_proxy_title">Прокси сервер за оптимизацију слика</string>
<string name="username">Корисничко име</string>
<string name="reader_info_bar_summary">Прикажите тренутно време и напредак читања на врху екрана</string>
<string name="authorization_optional">Ауторизација (опционално)</string>
@@ -80,8 +80,8 @@
<string name="details_button_tip">Притисните и држите дугме за читање да бисте видели више опција</string>
<string name="computing_">Рачунање…</string>
<string name="search">Претражи</string>
<string name="by_rating">Оцена</string>
<string name="sort_order">Редослед распоређивања</string>
<string name="by_rating">Оцени</string>
<string name="sort_order">Распореди по</string>
<string name="newest">Најновије</string>
<string name="light">Светла</string>
<string name="dark">Мрачна</string>
@@ -91,13 +91,13 @@
<string name="pages">Странице</string>
<string name="operation_not_supported">Ова операција није подржана</string>
<string name="error">Грешка</string>
<string name="new_chapters">Нова поглавља</string>
<string name="new_chapters">Новим поглављима</string>
<string name="notifications_settings">Подешавања обавештења</string>
<string name="new_version_s">Нова верзија: %s</string>
<string name="about_app_translation_summary">Преведите ову апликацију</string>
<string name="about_app_translation">Превод</string>
<string name="screenshots_policy">Политика снимања екрана</string>
<string name="clear_network_cache">Очистите мрежну кеш меморију</string>
<string name="clear_network_cache">Избриши мрежну кеш меморију</string>
<string name="password">Лозинка</string>
<string name="show_in_grid_view">Прикажи као мрежу</string>
<string name="page_saved">Сачувано</string>
@@ -105,17 +105,17 @@
<string name="processing_">Обрада…</string>
<string name="download_complete">Преузето</string>
<string name="downloads">Преузимања</string>
<string name="popular">Популарно</string>
<string name="updated">Ажуриран</string>
<string name="popular">Популарности</string>
<string name="updated">Ажурирано</string>
<string name="text_clear_history_prompt">Трајно избрисати сву историју читања\?</string>
<string name="remove">Уклони</string>
<string name="save_page">Сачувај страницу</string>
<string name="share_image">Подели слику</string>
<string name="text_file_not_supported">Изаберите ZIP датотеку или CBZ датотеку.</string>
<string name="no_description">Нема описа</string>
<string name="taps_on_edges">Додири по ивицама</string>
<string name="taps_on_edges">Додирни по ивицама</string>
<string name="reader_settings">Подешавања читача</string>
<string name="volume_buttons">Дугмад за јачину звука</string>
<string name="volume_buttons">Притисни дугмад за јачину звука</string>
<string name="notifications">Обавештења</string>
<string name="pages_cache">Кеш страница</string>
<string name="text_shelf_holder_secondary">Пронађите шта да читате у одељку „Преглед“</string>
@@ -132,7 +132,7 @@
<string name="favourites_categories">Омиљене категорије</string>
<string name="remove_category">Избриши</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">Омогућено је %1$d од %2$d</string>
<string name="clear">Очисти</string>
<string name="clear">Избриши</string>
<string name="text_history_holder_primary">Оно што прочитате биће приказано овде</string>
<string name="delete">Избриши</string>
<string name="search_history_cleared">Очишћено</string>
@@ -160,7 +160,7 @@
<string name="download_started">Преузимање је почело</string>
<string name="categories">Категорије</string>
<string name="tracking">Пратим</string>
<string name="progress">Напредак</string>
<string name="progress">Напредку</string>
<string name="cancel_all">Откажи све</string>
<string name="sync_host_description">Можеш да користиш сервер за синхронизацију који се самостално хостује или подразумевани. Не мењај ово ако ниси сигуран шта радиш.</string>
<string name="error_corrupted_file">Враћени су неважећи подаци или је датотека оштећена</string>
@@ -181,20 +181,20 @@
<string name="disable_all">Онемогући све</string>
<string name="tracker_wifi_only_summary">Не проверавај да ли постоје нова поглавља користећи мрежне везе са ограничењем</string>
<string name="error_multiple_states_not_supported">Овај извор манга не подржава филтрирање према више стања</string>
<string name="order_added">Додато</string>
<string name="order_added">Додатим</string>
<string name="source_summary_pattern">%1$s, %2$s</string>
<string name="enable_logging">Омогући дијагностику</string>
<string name="on_device">На уређају</string>
<string name="download_option_whole_manga">Цела манга</string>
<string name="settings_apply_restart_required">Поново покрени апликацију да би применио ове промене</string>
<string name="rotate_screen">Окрени екран</string>
<string name="text_clear_updates_feed_prompt">Трајно избрисати сву историју ажурирања?</string>
<string name="text_clear_updates_feed_prompt">Трајно избриши сву историју ажурирања?</string>
<string name="source_disabled">Извор је онемогућен</string>
<string name="backup_frequency">Учесталост прављења резервне копије</string>
<string name="clear_cookies_summary">Може помоћи у случају неких проблема. Сва овлашћења ће бити поништена</string>
<string name="enable_logging_summary">Снима неке радње у сврху отклањања грешака. Немојте ово укључивати ако нисте сигурни шта радите</string>
<string name="suggestions_enable">Омогући предлоге</string>
<string name="clear_feed">Очисти фид</string>
<string name="clear_feed">Очисти новости</string>
<string name="welcome">Добродошли</string>
<string name="chapters_empty">Нема поглавља у овој манги</string>
<string name="content_type_hentai">Хентаи</string>
@@ -208,7 +208,7 @@
<string name="import_completed">Увоз је завршен</string>
<string name="different_languages">Други језици</string>
<string name="show_reading_indicators">Показатељ током читања</string>
<string name="read_later">Читај касније</string>
<string name="read_later">За касније</string>
<string name="backup_saved">Резервна копија је сачувана</string>
<string name="local_manga_processing">Сачувана обрада манге</string>
<string name="user_agent">Заглавље КорисничкогАгент-а</string>
@@ -259,7 +259,7 @@
<string name="invalid_value_message">Погрешна вредност</string>
<string name="downloads_cancelled">Преузимања су отказана</string>
<string name="webtoon_zoom">Webtoon увећање</string>
<string name="various_languages">Разни језици</string>
<string name="various_languages">Сви језици</string>
<string name="removal_completed">Уклањање је завршено</string>
<string name="theme_name_miku">Мику</string>
<string name="edit">Уреди</string>
@@ -271,7 +271,7 @@
<string name="directories">Директоријуми</string>
<string name="local_manga_directories">Локални директорији манги</string>
<string name="manage_categories">Управљај категоријама</string>
<string name="update">Ажурирање</string>
<string name="update">Ажурирај</string>
<string name="scrobbling_empty_hint">Да бисте пратили напредак читања, изаберите Изборник → Прати на екрану са детаљима манге.</string>
<string name="removed_from_history">Уклоњено из историје</string>
<string name="no_manga_sources_found">Вашим упитом није пронађен ниједан доступан извор манге</string>
@@ -282,7 +282,7 @@
<string name="port">Прикључак</string>
<string name="color_correction_hint">Изабрана подешавања боја биће запамћена за ову мангу</string>
<string name="not_found_404">Садржај није пронађен или је уклоњен</string>
<string name="feed_will_update_soon">Ажурирање фида ће ускоро почети</string>
<string name="feed_will_update_soon">Ажурирање новости ће ускоро почети</string>
<string name="appwidget_recent_description">Ваша недавно прочитана манга</string>
<string name="got_it">Разумем</string>
<string name="type">Тип</string>
@@ -293,7 +293,7 @@
<string name="disable_battery_optimization_summary">Помаже код провера ажурирања у позадини</string>
<string name="auth_not_supported_by">Пријављивање на %s није подржано</string>
<string name="status_on_hold">На чекању</string>
<string name="last_2_hours">Последња 2 сата</string>
<string name="last_2_hours">Од последња 2 сата</string>
<string name="periodic_backups">Повремене резервне копије</string>
<string name="reader_zoom_buttons">Прикажи дугмад за увећање и смањивање</string>
<string name="name">Име</string>
@@ -311,7 +311,7 @@
<string name="main_screen_sections">Одељци главног екрана</string>
<string name="confirm_exit">Притисни Назад поново да изађеш</string>
<string name="scale_mode">Режим скалирања</string>
<string name="advanced">Напредно</string>
<string name="advanced">Напредна</string>
<string name="only_using_wifi">Само на ВиФи-ју</string>
<string name="sync_settings">Подешавања синхронизације</string>
<string name="back">Назад</string>
@@ -348,7 +348,7 @@
<string name="history_shortcuts_summary">Учини недавну мангу доступном дугим притиском на икону апликације</string>
<string name="automatic_scroll">Самостално померање</string>
<string name="download_option_first_n_chapters">Прво %s</string>
<string name="keep_screen_on">Остави екран укљученим</string>
<string name="keep_screen_on">Задржи екран укљученим</string>
<string name="reverse">Уназад</string>
<string name="track_sources">Потражи ажурирања</string>
<string name="paused">Заустављено</string>
@@ -389,7 +389,7 @@
<string name="sync">Синхронизација</string>
<string name="search_chapters">Пронађи поглавље</string>
<string name="exit_confirmation">Потврда изласка</string>
<string name="manual">Упутство</string>
<string name="manual">Ручном избору</string>
<string name="comics_archive">Архива стрипова</string>
<string name="custom_directory">Прилагођени директоријум</string>
<string name="more">Више</string>
@@ -440,7 +440,7 @@
<string name="chapters_will_removed_background">Поглавља ће бити уклоњена у позадини</string>
<string name="import_completed_hint">Можете да избришете оригиналну датотеку из складишта за уштеду простора</string>
<string name="theme_name_rikka">Рикка</string>
<string name="disabled">Онемогућено</string>
<string name="disabled">Онемогући</string>
<string name="long_ago">Давно</string>
<string name="reader_control_ltr_summary">Додиром на десну ивицу или притиском на десну стрелицу пребацује се на следећу страницу</string>
<string name="incognito_mode">Режим без чувања</string>
@@ -496,7 +496,7 @@
<string name="preparing_">Припрема се…</string>
<string name="exit_confirmation_summary">Притисни Назад двапут да би изашао из апликације</string>
<string name="bookmarks_removed">Обележивачи су уклоњени</string>
<string name="screenshots_block_nsfw">Блокирајте садржај за одрасле</string>
<string name="screenshots_block_nsfw">Блокирај садржај за одрасле</string>
<string name="enter_password">Унеси лозинку</string>
<string name="repeat_password">Поновите лозинку</string>
<string name="data_restored_success">Сви подаци су обновљени</string>
@@ -520,7 +520,7 @@
<string name="bookmarks">Обележивачи</string>
<string name="show_all">Покажи све</string>
<string name="this_month">Овог месеца</string>
<string name="today">Данас</string>
<string name="today">Од данас</string>
<string name="empty_favourite_categories">Нема омиљених категорија</string>
<string name="invalid_domain_message">Неважећи домен</string>
<string name="system_default">Подразумевано</string>
@@ -539,4 +539,8 @@
<string name="skip">Прескочи</string>
<string name="color_correction_apply_text">Ова подешавања се могу применити глобално или само на тренутну мангу. Ако се примењују глобално, појединачна подешавања неће бити замењена.</string>
<string name="grayscale">Сиви тон</string>
<string name="welcome_text">Изабери које изворе садржаја желиш да омогућиш. Ово можеш касније да промениш у подешавањима</string>
<string name="restore">Поврати</string>
<string name="backup_date_">Датум резервне копије: %s</string>
<string name="sync_auth">Пријавите се за синхронизацију налога</string>
</resources>

View File

@@ -539,4 +539,8 @@
<string name="color_correction_apply_text">Ці налаштування можна застосувати глобально або лише до поточної манґи. Користувацькі налаштування не будуть замінені, якщо застосовувати їх глобально.</string>
<string name="grayscale">Відтінки сірого</string>
<string name="disable_battery_optimization_summary_downloads">Може допомогти з початком завантаження, якщо у вас виникають проблеми з нею</string>
<string name="welcome_text">Виберіть, які джерела вмісту ви хочете включити. Це також можна налаштувати пізніше в налаштуваннях</string>
<string name="restore">Відновити</string>
<string name="backup_date_">Дата створення резервної копії: %s</string>
<string name="sync_auth">Увійдіть, щоб синхронізувати обліковий запис</string>
</resources>

View File

@@ -548,4 +548,6 @@
<string name="sync_auth">Login to sync account</string>
<string name="restore">Restore</string>
<string name="backup_date_">Backup date: %s</string>
<string name="state_upcoming">Upcoming</string>
<string name="by_name_reverse">Name reversed</string>
</resources>

View File

@@ -6,7 +6,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.2.0'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.49'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.50'
classpath 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.21-1.0.15'
}
}