Implement new manga sources settings list screen #78
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
package org.koitharu.kotatsu.base.ui.list.decor
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.utils.ext.getThemeDrawable
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
abstract class AbstractDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
|
||||
|
||||
private val bounds = Rect()
|
||||
private val divider = context.getThemeDrawable(android.R.attr.listDivider)
|
||||
|
||||
override fun getItemOffsets(
|
||||
outRect: Rect,
|
||||
view: View,
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State,
|
||||
) {
|
||||
outRect.set(0, divider?.intrinsicHeight ?: 0, 0, 0)
|
||||
}
|
||||
|
||||
// TODO implement for horizontal lists on demand
|
||||
override fun onDraw(canvas: Canvas, parent: RecyclerView, s: RecyclerView.State) {
|
||||
if (parent.layoutManager == null || divider == null) {
|
||||
return
|
||||
}
|
||||
canvas.save()
|
||||
val left: Int
|
||||
val right: Int
|
||||
if (parent.clipToPadding) {
|
||||
left = parent.paddingLeft
|
||||
right = parent.width - parent.paddingRight
|
||||
canvas.clipRect(
|
||||
left, parent.paddingTop, right,
|
||||
parent.height - parent.paddingBottom
|
||||
)
|
||||
} else {
|
||||
left = 0
|
||||
right = parent.width
|
||||
}
|
||||
|
||||
var previous: RecyclerView.ViewHolder? = null
|
||||
for (child in parent.children) {
|
||||
val holder = parent.getChildViewHolder(child)
|
||||
if (previous != null && shouldDrawDivider(previous, holder)) {
|
||||
parent.getDecoratedBoundsWithMargins(child, bounds)
|
||||
val top: Int = bounds.top + child.translationY.roundToInt()
|
||||
val bottom: Int = top + divider.intrinsicHeight
|
||||
divider.setBounds(left, top, right, bottom)
|
||||
divider.draw(canvas)
|
||||
}
|
||||
previous = holder
|
||||
}
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
protected abstract fun shouldDrawDivider(
|
||||
above: RecyclerView.ViewHolder,
|
||||
below: RecyclerView.ViewHolder,
|
||||
): Boolean
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
@@ -15,8 +14,9 @@ import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||
import org.koitharu.kotatsu.databinding.FragmentSettingsSourcesBinding
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigAdapter
|
||||
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigItem
|
||||
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigItemDecoration
|
||||
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
|
||||
class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
SourceConfigListener {
|
||||
@@ -45,7 +45,7 @@ class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
val sourcesAdapter = SourceConfigAdapter(this)
|
||||
with(binding.recyclerView) {
|
||||
setHasFixedSize(true)
|
||||
addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL))
|
||||
addItemDecoration(SourceConfigItemDecoration(view.context))
|
||||
adapter = sourcesAdapter
|
||||
reorderHelper.attachToRecyclerView(this)
|
||||
}
|
||||
@@ -79,7 +79,7 @@ class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
reorderHelper.startDrag(holder)
|
||||
}
|
||||
|
||||
override fun onHeaderClick(header: SourceConfigItem.LocaleHeader) {
|
||||
override fun onHeaderClick(header: SourceConfigItem.LocaleGroup) {
|
||||
viewModel.expandOrCollapse(header.localeId)
|
||||
}
|
||||
|
||||
@@ -91,16 +91,20 @@ class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
if (viewHolder.itemViewType != target.itemViewType) {
|
||||
return false
|
||||
}
|
||||
val oldPos = viewHolder.bindingAdapterPosition
|
||||
val newPos = target.bindingAdapterPosition
|
||||
viewModel.reorderSources(oldPos, newPos)
|
||||
return true
|
||||
}
|
||||
target: RecyclerView.ViewHolder,
|
||||
): Boolean = viewHolder.itemViewType == target.itemViewType && viewModel.reorderSources(
|
||||
viewHolder.bindingAdapterPosition,
|
||||
target.bindingAdapterPosition,
|
||||
)
|
||||
|
||||
override fun canDropOver(
|
||||
recyclerView: RecyclerView,
|
||||
current: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder,
|
||||
): Boolean = current.itemViewType == target.itemViewType && viewModel.canReorder(
|
||||
current.bindingAdapterPosition,
|
||||
target.bindingAdapterPosition,
|
||||
)
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
|
||||
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
package org.koitharu.kotatsu.settings.sources
|
||||
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaProviderFactory
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigItem
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
import org.koitharu.kotatsu.utils.ext.map
|
||||
import org.koitharu.kotatsu.utils.ext.move
|
||||
import org.koitharu.kotatsu.utils.ext.toTitleCase
|
||||
import java.util.*
|
||||
|
||||
private const val KEY_ENABLED = "!"
|
||||
|
||||
class SourcesSettingsViewModel(
|
||||
private val settings: AppSettings,
|
||||
) : BaseViewModel() {
|
||||
@@ -19,13 +26,23 @@ class SourcesSettingsViewModel(
|
||||
buildList()
|
||||
}
|
||||
|
||||
fun reorderSources(oldPos: Int, newPos: Int) {
|
||||
val snapshot = items.value?.toMutableList() ?: return
|
||||
Collections.swap(snapshot, oldPos, newPos)
|
||||
fun reorderSources(oldPos: Int, newPos: Int): Boolean {
|
||||
val snapshot = items.value?.toMutableList() ?: return false
|
||||
if ((snapshot[oldPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
|
||||
if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
|
||||
snapshot.move(oldPos, newPos)
|
||||
settings.sourcesOrder = snapshot.mapNotNull {
|
||||
(it as? SourceConfigItem.SourceItem)?.source?.ordinal
|
||||
}
|
||||
buildList()
|
||||
return true
|
||||
}
|
||||
|
||||
fun canReorder(oldPos: Int, newPos: Int): Boolean {
|
||||
val snapshot = items.value?.toMutableList() ?: return false
|
||||
if ((snapshot[oldPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
|
||||
if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
|
||||
return true
|
||||
}
|
||||
|
||||
fun setEnabled(source: MangaSource, isEnabled: Boolean) {
|
||||
@@ -49,11 +66,69 @@ class SourcesSettingsViewModel(
|
||||
private fun buildList() {
|
||||
val sources = MangaProviderFactory.getSources(settings, includeHidden = true)
|
||||
val hiddenSources = settings.hiddenSources
|
||||
items.value = sources.map {
|
||||
SourceConfigItem.SourceItem(
|
||||
source = it,
|
||||
isEnabled = it.name !in hiddenSources,
|
||||
)
|
||||
val map = sources.groupByTo(TreeMap(LocaleKeyComparator())) {
|
||||
if (it.name !in hiddenSources) {
|
||||
KEY_ENABLED
|
||||
} else {
|
||||
it.locale
|
||||
}
|
||||
}
|
||||
val result = ArrayList<SourceConfigItem>(sources.size + map.size + 1)
|
||||
val enabledSources = map.remove(KEY_ENABLED)
|
||||
if (!enabledSources.isNullOrEmpty()) {
|
||||
result += SourceConfigItem.Header(R.string.enabled_sources)
|
||||
enabledSources.mapTo(result) {
|
||||
SourceConfigItem.SourceItem(
|
||||
source = it,
|
||||
isEnabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (enabledSources?.size != sources.size) {
|
||||
result += SourceConfigItem.Header(R.string.available_sources)
|
||||
for ((key, list) in map) {
|
||||
val locale = if (key != null) {
|
||||
Locale(key)
|
||||
} else null
|
||||
list.sortBy { it.ordinal }
|
||||
val isExpanded = key in expandedGroups
|
||||
result += SourceConfigItem.LocaleGroup(
|
||||
localeId = key,
|
||||
title = locale?.getDisplayLanguage(locale)?.toTitleCase(locale),
|
||||
isExpanded = isExpanded,
|
||||
)
|
||||
if (isExpanded) {
|
||||
list.mapTo(result) {
|
||||
SourceConfigItem.SourceItem(
|
||||
source = it,
|
||||
isEnabled = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
items.value = result
|
||||
}
|
||||
|
||||
private class LocaleKeyComparator : Comparator<String?> {
|
||||
|
||||
private val deviceLocales = LocaleListCompat.getAdjustedDefault()
|
||||
.map { it.language }
|
||||
|
||||
override fun compare(a: String?, b: String?): Int {
|
||||
when {
|
||||
a == b -> return 0
|
||||
a == null -> return 1
|
||||
b == null -> return -1
|
||||
}
|
||||
val ai = deviceLocales.indexOf(a!!)
|
||||
val bi = deviceLocales.indexOf(b!!)
|
||||
return when {
|
||||
ai < 0 && bi < 0 -> a.compareTo(b)
|
||||
ai < 0 -> 1
|
||||
bi < 0 -> -1
|
||||
else -> ai.compareTo(bi)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package org.koitharu.kotatsu.settings.sources.adapter
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
|
||||
class SourceConfigAdapter(
|
||||
listener: SourceConfigListener,
|
||||
) : AsyncListDifferDelegationAdapter<SourceConfigItem>(
|
||||
SourceConfigDiffCallback(),
|
||||
sourceConfigHeaderDelegate(listener),
|
||||
sourceConfigHeaderDelegate(),
|
||||
sourceConfigGroupDelegate(listener),
|
||||
sourceConfigItemDelegate(listener),
|
||||
)
|
||||
@@ -4,14 +4,27 @@ import android.annotation.SuppressLint
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePaddingRelative
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.databinding.ItemExpandableBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemFilterHeaderBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemSourceConfigBinding
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
|
||||
fun sourceConfigHeaderDelegate(
|
||||
fun sourceConfigHeaderDelegate() = adapterDelegateViewBinding<SourceConfigItem.Header, SourceConfigItem, ItemFilterHeaderBinding>(
|
||||
{ layoutInflater, parent -> ItemFilterHeaderBinding.inflate(layoutInflater, parent, false) }
|
||||
) {
|
||||
|
||||
bind {
|
||||
binding.root.setText(item.titleResId)
|
||||
}
|
||||
}
|
||||
|
||||
fun sourceConfigGroupDelegate(
|
||||
listener: SourceConfigListener,
|
||||
) = adapterDelegateViewBinding<SourceConfigItem.LocaleHeader, SourceConfigItem, ItemExpandableBinding>(
|
||||
) = adapterDelegateViewBinding<SourceConfigItem.LocaleGroup, SourceConfigItem, ItemExpandableBinding>(
|
||||
{ layoutInflater, parent -> ItemExpandableBinding.inflate(layoutInflater, parent, false) }
|
||||
) {
|
||||
|
||||
@@ -57,5 +70,11 @@ fun sourceConfigItemDelegate(
|
||||
bind {
|
||||
binding.textViewTitle.text = item.source.title
|
||||
binding.switchToggle.isChecked = item.isEnabled
|
||||
binding.imageViewHandle.isVisible = item.isEnabled
|
||||
binding.imageViewConfig.isVisible = item.isEnabled
|
||||
binding.root.updatePaddingRelative(
|
||||
start = if (item.isEnabled) 0 else binding.imageViewHandle.paddingStart * 2,
|
||||
end = if (item.isEnabled) 0 else binding.imageViewConfig.paddingEnd,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,28 @@
|
||||
package org.koitharu.kotatsu.settings.sources.adapter
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
|
||||
class SourceConfigDiffCallback : DiffUtil.ItemCallback<SourceConfigItem>() {
|
||||
|
||||
override fun areItemsTheSame(oldItem: SourceConfigItem, newItem: SourceConfigItem): Boolean {
|
||||
return when {
|
||||
oldItem.javaClass != newItem.javaClass -> false
|
||||
oldItem is SourceConfigItem.LocaleHeader && newItem is SourceConfigItem.LocaleHeader -> {
|
||||
oldItem is SourceConfigItem.LocaleGroup && newItem is SourceConfigItem.LocaleGroup -> {
|
||||
oldItem.localeId == newItem.localeId
|
||||
}
|
||||
oldItem is SourceConfigItem.SourceItem && newItem is SourceConfigItem.SourceItem -> {
|
||||
oldItem.source == newItem.source
|
||||
}
|
||||
oldItem is SourceConfigItem.Header && newItem is SourceConfigItem.Header -> {
|
||||
oldItem.titleResId == newItem.titleResId
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: SourceConfigItem, newItem: SourceConfigItem): Boolean {
|
||||
return when {
|
||||
oldItem is SourceConfigItem.LocaleHeader && newItem is SourceConfigItem.LocaleHeader -> {
|
||||
oldItem.title == newItem.title && oldItem.isExpanded == newItem.isExpanded
|
||||
}
|
||||
oldItem is SourceConfigItem.SourceItem && newItem is SourceConfigItem.SourceItem -> {
|
||||
oldItem.isEnabled == newItem.isEnabled
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
override fun getChangePayload(oldItem: SourceConfigItem, newItem: SourceConfigItem) = Unit
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package org.koitharu.kotatsu.settings.sources.adapter
|
||||
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
|
||||
sealed interface SourceConfigItem {
|
||||
|
||||
data class LocaleHeader(
|
||||
val localeId: String?,
|
||||
val title: String?,
|
||||
val isExpanded: Boolean,
|
||||
) : SourceConfigItem
|
||||
|
||||
data class SourceItem(
|
||||
val source: MangaSource,
|
||||
val isEnabled: Boolean,
|
||||
) : SourceConfigItem
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.koitharu.kotatsu.settings.sources.adapter
|
||||
|
||||
import android.content.Context
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.AbstractDividerItemDecoration
|
||||
|
||||
class SourceConfigItemDecoration(context: Context) : AbstractDividerItemDecoration(context) {
|
||||
|
||||
override fun shouldDrawDivider(
|
||||
above: RecyclerView.ViewHolder,
|
||||
below: RecyclerView.ViewHolder,
|
||||
): Boolean {
|
||||
return above.itemViewType != 0 && below.itemViewType != 0
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.settings.sources.adapter
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
|
||||
interface SourceConfigListener {
|
||||
|
||||
@@ -10,5 +11,5 @@ interface SourceConfigListener {
|
||||
|
||||
fun onDragHandleTouch(holder: RecyclerView.ViewHolder)
|
||||
|
||||
fun onHeaderClick(header: SourceConfigItem.LocaleHeader)
|
||||
fun onHeaderClick(header: SourceConfigItem.LocaleGroup)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.koitharu.kotatsu.settings.sources.model
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
|
||||
sealed interface SourceConfigItem {
|
||||
|
||||
class Header(
|
||||
@StringRes val titleResId: Int,
|
||||
) : SourceConfigItem {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
other as Header
|
||||
return titleResId == other.titleResId
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = titleResId
|
||||
}
|
||||
|
||||
class LocaleGroup(
|
||||
val localeId: String?,
|
||||
val title: String?,
|
||||
val isExpanded: Boolean,
|
||||
) : SourceConfigItem {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as LocaleGroup
|
||||
|
||||
if (localeId != other.localeId) return false
|
||||
if (title != other.title) return false
|
||||
if (isExpanded != other.isExpanded) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = localeId?.hashCode() ?: 0
|
||||
result = 31 * result + (title?.hashCode() ?: 0)
|
||||
result = 31 * result + isExpanded.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class SourceItem(
|
||||
val source: MangaSource,
|
||||
val isEnabled: Boolean,
|
||||
) : SourceConfigItem {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SourceItem
|
||||
|
||||
if (source != other.source) return false
|
||||
if (isEnabled != other.isEnabled) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = source.hashCode()
|
||||
result = 31 * result + isEnabled.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.utils.ext
|
||||
import androidx.collection.ArrayMap
|
||||
import androidx.collection.ArraySet
|
||||
import androidx.collection.LongSparseArray
|
||||
import java.util.*
|
||||
|
||||
fun <T> MutableCollection<T>.replaceWith(subject: Iterable<T>) {
|
||||
clear()
|
||||
@@ -72,4 +73,12 @@ fun <T, K> Collection<T>.isDistinctBy(selector: (T) -> K): Boolean {
|
||||
}
|
||||
}
|
||||
return set.size == size
|
||||
}
|
||||
|
||||
fun <T> MutableList<T>.move(sourceIndex: Int, targetIndex: Int) {
|
||||
if (sourceIndex <= targetIndex) {
|
||||
Collections.rotate(subList(sourceIndex, targetIndex + 1), -1)
|
||||
} else {
|
||||
Collections.rotate(subList(targetIndex, sourceIndex + 1), 1)
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@
|
||||
<ImageView
|
||||
android:id="@+id/imageView_handle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="?listPreferredItemPaddingStart"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="?listPreferredItemPaddingStart"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_reorder_handle" />
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
<ImageView
|
||||
android:id="@+id/imageView_config"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/settings"
|
||||
android:padding="?listPreferredItemPaddingEnd"
|
||||
android:paddingHorizontal="?listPreferredItemPaddingEnd"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_settings" />
|
||||
|
||||
|
||||
@@ -246,4 +246,6 @@
|
||||
<string name="exclude_nsfw_from_history">Исключить NSFW мангу из истории</string>
|
||||
<string name="error_empty_name">Имя не может быть пустым</string>
|
||||
<string name="show_pages_numbers">Показывать номера страниц</string>
|
||||
<string name="enabled_sources">Включенные источники</string>
|
||||
<string name="available_sources">Доступные источники</string>
|
||||
</resources>
|
||||
@@ -247,4 +247,6 @@
|
||||
<string name="exclude_nsfw_from_history">Exclude NSFW manga from history</string>
|
||||
<string name="error_empty_name">Name should not be empty</string>
|
||||
<string name="show_pages_numbers">Show pages numbers</string>
|
||||
<string name="enabled_sources">Enabled sources</string>
|
||||
<string name="available_sources">Available sources</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user