Add image server option to reader config sheet
This commit is contained in:
@@ -48,7 +48,7 @@ class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig
|
|||||||
is ConfigKey.ShowSuspiciousContent -> putBoolean(key.key, value as Boolean)
|
is ConfigKey.ShowSuspiciousContent -> putBoolean(key.key, value as Boolean)
|
||||||
is ConfigKey.UserAgent -> putString(key.key, (value as String?)?.sanitizeHeaderValue())
|
is ConfigKey.UserAgent -> putString(key.key, (value as String?)?.sanitizeHeaderValue())
|
||||||
is ConfigKey.SplitByTranslations -> putBoolean(key.key, value as Boolean)
|
is ConfigKey.SplitByTranslations -> putBoolean(key.key, value as Boolean)
|
||||||
is ConfigKey.PreferredImageServer -> putString(key.key, value as String?)
|
is ConfigKey.PreferredImageServer -> putString(key.key, value as String? ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,4 +69,11 @@ fun <T> Iterable<T>.sortedWithSafe(comparator: Comparator<in T>): List<T> = try
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Collection<*>?.sizeOrZero() = if (this == null) 0 else size
|
fun Collection<*>?.sizeOrZero() = this?.size ?: 0
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
inline fun <T, reified R> Collection<T>.mapToArray(transform: (T) -> R): Array<R> {
|
||||||
|
val result = arrayOfNulls<R>(size)
|
||||||
|
forEachIndexed { index, t -> result[index] = transform(t) }
|
||||||
|
return result as Array<R>
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,12 +12,16 @@ import kotlinx.coroutines.CancellableContinuation
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.joinAll
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import org.koitharu.kotatsu.core.util.AcraCoroutineErrorHandler
|
import org.koitharu.kotatsu.core.util.AcraCoroutineErrorHandler
|
||||||
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
||||||
|
import org.koitharu.kotatsu.parsers.util.cancelAll
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
@@ -90,3 +94,10 @@ fun <T> Deferred<T>.peek(): T? = if (isCompleted) {
|
|||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("SuspendFunctionOnCoroutineScope")
|
||||||
|
suspend fun CoroutineScope.cancelChildrenAndJoin(cause: CancellationException? = null) {
|
||||||
|
val jobs = coroutineContext[Job]?.children?.toList() ?: return
|
||||||
|
jobs.cancelAll(cause)
|
||||||
|
jobs.joinAll()
|
||||||
|
}
|
||||||
|
|||||||
@@ -82,6 +82,13 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun clear() {
|
||||||
|
val cache = lruCache.get()
|
||||||
|
runInterruptible(Dispatchers.IO) {
|
||||||
|
cache.clearCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getAvailableSize(): Long = runCatchingCancellable {
|
private suspend fun getAvailableSize(): Long = runCatchingCancellable {
|
||||||
val statFs = StatFs(cacheDir.get().absolutePath)
|
val statFs = StatFs(cacheDir.get().absolutePath)
|
||||||
statFs.availableBytes
|
statFs.availableBytes
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
|||||||
import org.koitharu.kotatsu.core.util.FileSize
|
import org.koitharu.kotatsu.core.util.FileSize
|
||||||
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
||||||
import org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP
|
import org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.cancelChildrenAndJoin
|
||||||
import org.koitharu.kotatsu.core.util.ext.compressToPNG
|
import org.koitharu.kotatsu.core.util.ext.compressToPNG
|
||||||
import org.koitharu.kotatsu.core.util.ext.ensureRamAtLeast
|
import org.koitharu.kotatsu.core.util.ext.ensureRamAtLeast
|
||||||
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
|
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
|
||||||
@@ -168,6 +169,14 @@ class PageLoader @Inject constructor(
|
|||||||
return getRepository(page.source).getPageUrl(page)
|
return getRepository(page.source).getPageUrl(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun invalidate(clearCache: Boolean) {
|
||||||
|
tasks.clear()
|
||||||
|
loaderScope.cancelChildrenAndJoin()
|
||||||
|
if (clearCache) {
|
||||||
|
cache.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onIdle() = loaderScope.launch {
|
private fun onIdle() = loaderScope.launch {
|
||||||
prefetchLock.withLock {
|
prefetchLock.withLock {
|
||||||
while (prefetchQueue.isNotEmpty()) {
|
while (prefetchQueue.isNotEmpty()) {
|
||||||
|
|||||||
@@ -291,17 +291,28 @@ constructor(
|
|||||||
val prevJob = loadingJob
|
val prevJob = loadingJob
|
||||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||||
prevJob?.cancelAndJoin()
|
prevJob?.cancelAndJoin()
|
||||||
val currentChapterId = currentState.requireValue().chapterId
|
val prevState = currentState.requireValue()
|
||||||
val allChapters = checkNotNull(manga).allChapters
|
val newChapterId = if (delta != 0) {
|
||||||
var index = allChapters.indexOfFirst { x -> x.id == currentChapterId }
|
val allChapters = checkNotNull(manga).allChapters
|
||||||
if (index < 0) {
|
var index = allChapters.indexOfFirst { x -> x.id == prevState.chapterId }
|
||||||
return@launchLoadingJob
|
if (index < 0) {
|
||||||
|
return@launchLoadingJob
|
||||||
|
}
|
||||||
|
index += delta
|
||||||
|
(allChapters.getOrNull(index) ?: return@launchLoadingJob).id
|
||||||
|
} else {
|
||||||
|
prevState.chapterId
|
||||||
}
|
}
|
||||||
index += delta
|
|
||||||
val newChapterId = (allChapters.getOrNull(index) ?: return@launchLoadingJob).id
|
|
||||||
content.value = ReaderContent(emptyList(), null)
|
content.value = ReaderContent(emptyList(), null)
|
||||||
chaptersLoader.loadSingleChapter(newChapterId)
|
chaptersLoader.loadSingleChapter(newChapterId)
|
||||||
content.value = ReaderContent(chaptersLoader.snapshot(), ReaderState(newChapterId, 0, 0))
|
content.value = ReaderContent(
|
||||||
|
chaptersLoader.snapshot(),
|
||||||
|
ReaderState(
|
||||||
|
chapterId = newChapterId,
|
||||||
|
page = if (delta == 0) prevState.page else 0,
|
||||||
|
scroll = if (delta == 0) prevState.scroll else 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package org.koitharu.kotatsu.reader.ui.config
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
|
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.mapToArray
|
||||||
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.util.SuspendLazy
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
|
class ImageServerDelegate(
|
||||||
|
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
|
private val mangaSource: MangaSource?,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val repositoryLazy = SuspendLazy {
|
||||||
|
mangaRepositoryFactory.create(checkNotNull(mangaSource)) as RemoteMangaRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun isAvailable() = withContext(Dispatchers.Default) {
|
||||||
|
repositoryLazy.tryGet().map { repository ->
|
||||||
|
repository.getConfigKeys().any { it is ConfigKey.PreferredImageServer }
|
||||||
|
}.getOrDefault(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getValue(): String? = withContext(Dispatchers.Default) {
|
||||||
|
repositoryLazy.tryGet().map { repository ->
|
||||||
|
val key = repository.getConfigKeys().firstNotNullOfOrNull { it as? ConfigKey.PreferredImageServer }
|
||||||
|
if (key != null) {
|
||||||
|
key.presetValues[repository.getConfig()[key]]
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun showDialog(context: Context): Boolean {
|
||||||
|
val repository = withContext(Dispatchers.Default) {
|
||||||
|
repositoryLazy.tryGet().getOrNull()
|
||||||
|
} ?: return false
|
||||||
|
val key = repository.getConfigKeys().firstNotNullOfOrNull {
|
||||||
|
it as? ConfigKey.PreferredImageServer
|
||||||
|
} ?: return false
|
||||||
|
val entries = key.presetValues.values.mapToArray {
|
||||||
|
it ?: context.getString(R.string.automatic)
|
||||||
|
}
|
||||||
|
val entryValues = key.presetValues.keys.toTypedArray()
|
||||||
|
val config = repository.getConfig()
|
||||||
|
val initialValue = config[key]
|
||||||
|
var currentValue = initialValue
|
||||||
|
val changed = suspendCancellableCoroutine { cont ->
|
||||||
|
val dialog = MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(R.string.image_server)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setSingleChoiceItems(entries, entryValues.indexOf(initialValue)) { _, i ->
|
||||||
|
currentValue = entryValues[i]
|
||||||
|
}.setNegativeButton(android.R.string.cancel) { dialog, _ ->
|
||||||
|
dialog.cancel()
|
||||||
|
}.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
if (currentValue != initialValue) {
|
||||||
|
config[key] = currentValue
|
||||||
|
cont.resume(true)
|
||||||
|
} else {
|
||||||
|
cont.resume(false)
|
||||||
|
}
|
||||||
|
}.setOnCancelListener {
|
||||||
|
cont.resume(false)
|
||||||
|
}.create()
|
||||||
|
dialog.show()
|
||||||
|
cont.invokeOnCancellation {
|
||||||
|
dialog.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
repository.invalidateCache()
|
||||||
|
}
|
||||||
|
return changed
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,10 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||||
@@ -29,6 +31,7 @@ import org.koitharu.kotatsu.core.util.ext.showDistinct
|
|||||||
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
||||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||||
import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding
|
import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding
|
||||||
|
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||||
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
|
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
|
||||||
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity
|
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity
|
||||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||||
@@ -47,7 +50,14 @@ class ReaderConfigSheet :
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var orientationHelper: ScreenOrientationHelper
|
lateinit var orientationHelper: ScreenOrientationHelper
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var mangaRepositoryFactory: MangaRepository.Factory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var pageLoader: PageLoader
|
||||||
|
|
||||||
private lateinit var mode: ReaderMode
|
private lateinit var mode: ReaderMode
|
||||||
|
private lateinit var imageServerDelegate: ImageServerDelegate
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var settings: AppSettings
|
lateinit var settings: AppSettings
|
||||||
@@ -57,6 +67,10 @@ class ReaderConfigSheet :
|
|||||||
mode = arguments?.getInt(ARG_MODE)
|
mode = arguments?.getInt(ARG_MODE)
|
||||||
?.let { ReaderMode.valueOf(it) }
|
?.let { ReaderMode.valueOf(it) }
|
||||||
?: ReaderMode.STANDARD
|
?: ReaderMode.STANDARD
|
||||||
|
imageServerDelegate = ImageServerDelegate(
|
||||||
|
mangaRepositoryFactory = mangaRepositoryFactory,
|
||||||
|
mangaSource = viewModel.manga?.toManga()?.source,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewBinding(
|
override fun onCreateViewBinding(
|
||||||
@@ -83,11 +97,20 @@ class ReaderConfigSheet :
|
|||||||
binding.buttonSavePage.setOnClickListener(this)
|
binding.buttonSavePage.setOnClickListener(this)
|
||||||
binding.buttonScreenRotate.setOnClickListener(this)
|
binding.buttonScreenRotate.setOnClickListener(this)
|
||||||
binding.buttonSettings.setOnClickListener(this)
|
binding.buttonSettings.setOnClickListener(this)
|
||||||
|
binding.buttonImageServer.setOnClickListener(this)
|
||||||
binding.buttonColorFilter.setOnClickListener(this)
|
binding.buttonColorFilter.setOnClickListener(this)
|
||||||
binding.sliderTimer.addOnChangeListener(this)
|
binding.sliderTimer.addOnChangeListener(this)
|
||||||
binding.switchScrollTimer.setOnCheckedChangeListener(this)
|
binding.switchScrollTimer.setOnCheckedChangeListener(this)
|
||||||
binding.switchDoubleReader.setOnCheckedChangeListener(this)
|
binding.switchDoubleReader.setOnCheckedChangeListener(this)
|
||||||
|
|
||||||
|
viewLifecycleScope.launch {
|
||||||
|
val isAvailable = imageServerDelegate.isAvailable()
|
||||||
|
if (isAvailable) {
|
||||||
|
bindImageServerTitle()
|
||||||
|
}
|
||||||
|
binding.buttonImageServer.isVisible = isAvailable
|
||||||
|
}
|
||||||
|
|
||||||
settings.observeAsStateFlow(
|
settings.observeAsStateFlow(
|
||||||
scope = lifecycleScope + Dispatchers.Default,
|
scope = lifecycleScope + Dispatchers.Default,
|
||||||
key = AppSettings.KEY_READER_AUTOSCROLL_SPEED,
|
key = AppSettings.KEY_READER_AUTOSCROLL_SPEED,
|
||||||
@@ -124,6 +147,14 @@ class ReaderConfigSheet :
|
|||||||
val manga = viewModel.manga?.toManga() ?: return
|
val manga = viewModel.manga?.toManga() ?: return
|
||||||
startActivity(ColorFilterConfigActivity.newIntent(v.context, manga, page))
|
startActivity(ColorFilterConfigActivity.newIntent(v.context, manga, page))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.button_image_server -> viewLifecycleScope.launch {
|
||||||
|
if (imageServerDelegate.showDialog(v.context)) {
|
||||||
|
bindImageServerTitle()
|
||||||
|
pageLoader.invalidate(clearCache = true)
|
||||||
|
viewModel.switchChapterBy(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +225,14 @@ class ReaderConfigSheet :
|
|||||||
switch.setOnCheckedChangeListener(this)
|
switch.setOnCheckedChangeListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun bindImageServerTitle() {
|
||||||
|
viewBinding?.buttonImageServer?.text = getString(
|
||||||
|
R.string.inline_preference_pattern,
|
||||||
|
getString(R.string.image_server),
|
||||||
|
imageServerDelegate.getValue() ?: getString(R.string.automatic),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
|
|
||||||
var isAutoScrollEnabled: Boolean
|
var isAutoScrollEnabled: Boolean
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.preference.PreferenceFragmentCompat
|
|||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.mapToArray
|
||||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||||
import org.koitharu.kotatsu.settings.utils.AutoCompleteTextViewPreference
|
import org.koitharu.kotatsu.settings.utils.AutoCompleteTextViewPreference
|
||||||
@@ -102,10 +103,3 @@ fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: RemoteMang
|
|||||||
private fun Array<out String>.toStringArray(): Array<String> {
|
private fun Array<out String>.toStringArray(): Array<String> {
|
||||||
return Array(size) { i -> this[i] as? String ?: "" }
|
return Array(size) { i -> this[i] as? String ?: "" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private inline fun <T, reified R> Collection<T>.mapToArray(transform: (T) -> R): Array<R> {
|
|
||||||
val result = arrayOfNulls<R>(size)
|
|
||||||
forEachIndexed { index, t -> result[index] = transform(t) }
|
|
||||||
return result as Array<R>
|
|
||||||
}
|
|
||||||
|
|||||||
12
app/src/main/res/drawable/ic_images.xml
Normal file
12
app/src/main/res/drawable/ic_images.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:pathData="M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3M15.96,10.29L13.21,13.83L11.25,11.47L8.5,15H19.5L15.96,10.29Z" />
|
||||||
|
</vector>
|
||||||
@@ -210,6 +210,19 @@
|
|||||||
android:textAppearance="?attr/textAppearanceButton"
|
android:textAppearance="?attr/textAppearanceButton"
|
||||||
app:drawableStartCompat="@drawable/ic_appearance" />
|
app:drawableStartCompat="@drawable/ic_appearance" />
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
|
||||||
|
android:id="@+id/button_image_server"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||||
|
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
||||||
|
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||||
|
android:text="@string/image_server"
|
||||||
|
android:textAppearance="?attr/textAppearanceButton"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:drawableStartCompat="@drawable/ic_images"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
|
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
|
||||||
android:id="@+id/button_settings"
|
android:id="@+id/button_settings"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -655,4 +655,5 @@
|
|||||||
<string name="all_languages">All languages</string>
|
<string name="all_languages">All languages</string>
|
||||||
<string name="screenshots_block_incognito">Block when incognito mode</string>
|
<string name="screenshots_block_incognito">Block when incognito mode</string>
|
||||||
<string name="image_server">Preferred image server</string>
|
<string name="image_server">Preferred image server</string>
|
||||||
|
<string name="inline_preference_pattern" translatable="false">%1$s: %2$s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user