Manage sources from extension details (closes #3152)

This commit is contained in:
arkon 2020-05-31 16:23:51 -04:00
parent 0bf14fd31c
commit 54cfb2acdf
9 changed files with 253 additions and 322 deletions

View File

@ -1,23 +1,61 @@
package eu.kanade.tachiyomi.ui.browse.extension package eu.kanade.tachiyomi.ui.browse.extension
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
import androidx.preference.DialogPreference
import androidx.preference.EditTextPreference
import androidx.preference.EditTextPreferenceDialogController
import androidx.preference.ListPreference
import androidx.preference.ListPreferenceDialogController
import androidx.preference.MultiSelectListPreference
import androidx.preference.MultiSelectListPreferenceDialogController
import androidx.preference.Preference
import androidx.preference.PreferenceGroupAdapter
import androidx.preference.PreferenceManager
import androidx.preference.PreferenceScreen
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import androidx.recyclerview.widget.LinearLayoutManager
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore
import eu.kanade.tachiyomi.databinding.ExtensionDetailControllerBinding import eu.kanade.tachiyomi.databinding.ExtensionDetailControllerBinding
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.util.preference.checkBoxPreference
import eu.kanade.tachiyomi.util.preference.onChange
import eu.kanade.tachiyomi.util.preference.preference
import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.android.view.clicks
import uy.kohesive.injekt.injectLazy
@SuppressLint("RestrictedApi")
class ExtensionDetailsController(bundle: Bundle? = null) : class ExtensionDetailsController(bundle: Bundle? = null) :
NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle) { NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle),
PreferenceManager.OnDisplayPreferenceDialogListener,
DialogPreference.TargetFragment {
private val preferences: PreferencesHelper by injectLazy()
private var preferenceScreen: PreferenceScreen? = null
private var lastOpenPreferencePosition: Int? = null
constructor(pkgName: String) : this( constructor(pkgName: String) : this(
Bundle().apply { Bundle().apply {
@ -25,8 +63,13 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
} }
) )
init {
setHasOptionsMenu(true)
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = ExtensionDetailControllerBinding.inflate(inflater) val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
binding = ExtensionDetailControllerBinding.inflate(themedInflater)
return binding.root return binding.root
} }
@ -65,25 +108,186 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
binding.extensionWarningBanner.setText(R.string.unofficial_extension_message) binding.extensionWarningBanner.setText(R.string.unofficial_extension_message)
} }
if (presenter.extension?.sources?.find { it is ConfigurableSource } != null) { initPreferences(context, extension)
binding.extensionPrefs.visible() }
binding.extensionPrefs.clicks()
.onEach { openPreferences() } private fun initPreferences(context: Context, extension: Extension.Installed) {
.launchIn(scope) val themedContext by lazy { getPreferenceThemeContext() }
val manager = PreferenceManager(themedContext)
manager.preferenceDataStore = EmptyPreferenceDataStore()
manager.onDisplayPreferenceDialogListener = this
val screen = manager.createPreferenceScreen(themedContext)
preferenceScreen = screen
with(screen) {
extension.sources
.groupBy { (it as CatalogueSource).lang }
.toSortedMap(compareBy { LocaleHelper.getSourceDisplayName(it, context) })
.forEach {
preferenceCategory {
title = LocaleHelper.getSourceDisplayName(it.key, context)
it.value
.sortedWith(compareBy({ !it.isEnabled() }, { it.name }))
.forEach { source ->
val sourcePrefs = mutableListOf<Preference>()
// Source enable/disable
checkBoxPreference {
key = getSourceKey(source.id)
title = source.toString()
isPersistent = false
isChecked = source.isEnabled()
onChange { newValue ->
val checked = newValue as Boolean
toggleSource(source, checked)
true
}
// React to enable/disable all changes
preferences.hiddenCatalogues().asFlow()
.onEach {
val enabled = source.isEnabled()
isChecked = enabled
sourcePrefs.forEach { pref -> pref.isVisible = enabled }
}
.launchIn(scope)
}
// Source preferences
if (source is ConfigurableSource) {
// TODO
val dataStore = SharedPreferencesDataStore(/*if (source is HttpSource) {
source.preferences
} else {*/
context.getSharedPreferences(getSourceKey(source.id), Context.MODE_PRIVATE)
/*}*/
)
val newScreen = screen.preferenceManager.createPreferenceScreen(context)
source.setupPreferenceScreen(newScreen)
// Reparent the preferences
while (newScreen.preferenceCount != 0) {
val pref = newScreen.getPreference(0)
sourcePrefs.add(pref)
pref.preferenceDataStore = dataStore
pref.order = Int.MAX_VALUE // reset to default order
pref.isVisible = source.isEnabled()
newScreen.removePreference(pref)
screen.addPreference(pref)
}
}
}
}
}
} }
binding.extensionPrefsRecycler.layoutManager = LinearLayoutManager(context)
binding.extensionPrefsRecycler.adapter = PreferenceGroupAdapter(screen)
binding.extensionPrefsRecycler.addItemDecoration(DividerItemDecoration(context, VERTICAL))
}
override fun onSaveInstanceState(outState: Bundle) {
lastOpenPreferencePosition?.let { outState.putInt(LASTOPENPREFERENCE_KEY, it) }
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
lastOpenPreferencePosition = savedInstanceState.get(LASTOPENPREFERENCE_KEY) as? Int
}
override fun onDestroyView(view: View) {
preferenceScreen = null
super.onDestroyView(view)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.extension_details, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_enable_all -> toggleAllSources(true)
R.id.action_disable_all -> toggleAllSources(false)
}
return super.onOptionsItemSelected(item)
} }
fun onExtensionUninstalled() { fun onExtensionUninstalled() {
router.popCurrentController() router.popCurrentController()
} }
private fun openPreferences() { private fun toggleAllSources(enable: Boolean) {
router.pushController( presenter.extension?.sources?.forEach { toggleSource(it, enable) }
ExtensionPreferencesController(presenter.extension!!.pkgName).withFadeTransaction() }
private fun toggleSource(source: Source, enable: Boolean) {
val current = preferences.hiddenCatalogues().get()
preferences.hiddenCatalogues().set(
if (enable) {
current - source.id.toString()
} else {
current + source.id.toString()
}
) )
} }
private fun Source.isEnabled(): Boolean {
return id.toString() !in preferences.hiddenCatalogues().get()
}
private fun getSourceKey(sourceId: Long): String {
return "source_$sourceId"
}
private fun getPreferenceThemeContext(): Context {
val tv = TypedValue()
activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
return ContextThemeWrapper(activity, tv.resourceId)
}
override fun onDisplayPreferenceDialog(preference: Preference) {
if (!isAttached) return
val screen = preference.parent!!
lastOpenPreferencePosition = (0 until screen.preferenceCount).indexOfFirst {
screen.getPreference(it) === preference
}
val f = when (preference) {
is EditTextPreference ->
EditTextPreferenceDialogController
.newInstance(preference.getKey())
is ListPreference ->
ListPreferenceDialogController
.newInstance(preference.getKey())
is MultiSelectListPreference ->
MultiSelectListPreferenceDialogController
.newInstance(preference.getKey())
else -> throw IllegalArgumentException(
"Tried to display dialog for unknown " +
"preference type. Did you forget to override onDisplayPreferenceDialog()?"
)
}
f.targetController = this
f.showDialog(router)
}
@Suppress("UNCHECKED_CAST")
override fun <T : Preference> findPreference(key: CharSequence): T? {
// We track [lastOpenPreferencePosition] when displaying the dialog
// [key] isn't useful since there may be duplicates
return preferenceScreen!!.getPreference(lastOpenPreferencePosition!!) as T
}
private companion object { private companion object {
const val PKGNAME_KEY = "pkg_name" const val PKGNAME_KEY = "pkg_name"
const val LASTOPENPREFERENCE_KEY = "last_open_preference"
} }
} }

View File

@ -36,11 +36,13 @@ class ExtensionFilterController : SettingsController() {
val checked = newValue as Boolean val checked = newValue as Boolean
val currentActiveLangs = preferences.enabledLanguages().get() val currentActiveLangs = preferences.enabledLanguages().get()
preferences.enabledLanguages().set(if (checked) { preferences.enabledLanguages().set(
currentActiveLangs + it if (checked) {
} else { currentActiveLangs + it
currentActiveLangs - it } else {
}) currentActiveLangs - it
}
)
true true
} }
} }

View File

@ -1,196 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.extension
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.preference.DialogPreference
import androidx.preference.EditTextPreference
import androidx.preference.EditTextPreferenceDialogController
import androidx.preference.ListPreference
import androidx.preference.ListPreferenceDialogController
import androidx.preference.MultiSelectListPreference
import androidx.preference.MultiSelectListPreferenceDialogController
import androidx.preference.Preference
import androidx.preference.PreferenceGroupAdapter
import androidx.preference.PreferenceManager
import androidx.preference.PreferenceScreen
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import androidx.recyclerview.widget.LinearLayoutManager
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore
import eu.kanade.tachiyomi.databinding.ExtensionPreferencesControllerBinding
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.util.preference.preferenceCategory
import timber.log.Timber
@SuppressLint("RestrictedApi")
class ExtensionPreferencesController(bundle: Bundle? = null) :
NucleusController<ExtensionPreferencesControllerBinding, ExtensionPreferencesPresenter>(bundle),
PreferenceManager.OnDisplayPreferenceDialogListener,
DialogPreference.TargetFragment {
private var lastOpenPreferencePosition: Int? = null
private var preferenceScreen: PreferenceScreen? = null
constructor(pkgName: String) : this(
Bundle().apply {
putString(PKGNAME_KEY, pkgName)
}
)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
binding = ExtensionPreferencesControllerBinding.inflate(themedInflater)
return binding.root
}
override fun createPresenter(): ExtensionPreferencesPresenter {
return ExtensionPreferencesPresenter(args.getString(PKGNAME_KEY)!!)
}
override fun getTitle(): String? {
return resources?.getString(R.string.label_extension_info)
}
@SuppressLint("PrivateResource")
override fun onViewCreated(view: View) {
super.onViewCreated(view)
val extension = presenter.extension ?: return
val context = view.context
val themedContext by lazy { getPreferenceThemeContext() }
val manager = PreferenceManager(themedContext)
manager.preferenceDataStore = EmptyPreferenceDataStore()
manager.onDisplayPreferenceDialogListener = this
val screen = manager.createPreferenceScreen(themedContext)
preferenceScreen = screen
val multiSource = extension.sources.size > 1
extension.sources
.filterIsInstance<ConfigurableSource>()
.forEach { source ->
try {
addPreferencesForSource(screen, source, multiSource)
} catch (e: AbstractMethodError) {
Timber.e("Source did not implement [addPreferencesForSource]: ${source.name}")
}
}
manager.setPreferences(screen)
binding.extensionPrefsRecycler.layoutManager = LinearLayoutManager(context)
binding.extensionPrefsRecycler.adapter = PreferenceGroupAdapter(screen)
binding.extensionPrefsRecycler.addItemDecoration(DividerItemDecoration(context, VERTICAL))
if (screen.preferenceCount == 0) {
binding.extensionPrefsEmptyView.show(R.string.ext_empty_preferences)
}
}
override fun onDestroyView(view: View) {
preferenceScreen = null
super.onDestroyView(view)
}
override fun onSaveInstanceState(outState: Bundle) {
lastOpenPreferencePosition?.let { outState.putInt(LASTOPENPREFERENCE_KEY, it) }
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
lastOpenPreferencePosition = savedInstanceState.get(LASTOPENPREFERENCE_KEY) as? Int
}
private fun addPreferencesForSource(screen: PreferenceScreen, source: Source, multiSource: Boolean) {
val context = screen.context
// TODO
val dataStore = SharedPreferencesDataStore(/*if (source is HttpSource) {
source.preferences
} else {*/
context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE)
/*}*/
)
if (source is ConfigurableSource) {
if (multiSource) {
screen.preferenceCategory {
title = source.toString()
}
}
val newScreen = screen.preferenceManager.createPreferenceScreen(context)
source.setupPreferenceScreen(newScreen)
// Reparent the preferences
while (newScreen.preferenceCount != 0) {
val pref = newScreen.getPreference(0)
pref.isIconSpaceReserved = false
pref.preferenceDataStore = dataStore
pref.order = Int.MAX_VALUE // reset to default order
newScreen.removePreference(pref)
screen.addPreference(pref)
}
}
}
private fun getPreferenceThemeContext(): Context {
val tv = TypedValue()
activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
return ContextThemeWrapper(activity, tv.resourceId)
}
override fun onDisplayPreferenceDialog(preference: Preference) {
if (!isAttached) return
val screen = preference.parent!!
lastOpenPreferencePosition = (0 until screen.preferenceCount).indexOfFirst {
screen.getPreference(it) === preference
}
val f = when (preference) {
is EditTextPreference ->
EditTextPreferenceDialogController
.newInstance(preference.getKey())
is ListPreference ->
ListPreferenceDialogController
.newInstance(preference.getKey())
is MultiSelectListPreference ->
MultiSelectListPreferenceDialogController
.newInstance(preference.getKey())
else -> throw IllegalArgumentException(
"Tried to display dialog for unknown " +
"preference type. Did you forget to override onDisplayPreferenceDialog()?"
)
}
f.targetController = this
f.showDialog(router)
}
@Suppress("UNCHECKED_CAST")
override fun <T : Preference> findPreference(key: CharSequence): T? {
// We track [lastOpenPreferencePosition] when displaying the dialog
// [key] isn't useful since there may be duplicates
return preferenceScreen!!.getPreference(lastOpenPreferencePosition!!) as T
}
private companion object {
const val PKGNAME_KEY = "pkg_name"
const val LASTOPENPREFERENCE_KEY = "last_open_preference"
}
}

View File

@ -1,14 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.extension
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ExtensionPreferencesPresenter(
val pkgName: String,
extensionManager: ExtensionManager = Injekt.get()
) : BasePresenter<ExtensionPreferencesController>() {
val extension = extensionManager.installedExtensions.find { it.pkgName == pkgName }
}

View File

@ -1,15 +1,11 @@
package eu.kanade.tachiyomi.ui.setting package eu.kanade.tachiyomi.ui.setting
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.preference.CheckBoxPreference
import androidx.preference.PreferenceGroup
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.icon
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.preference.onChange import eu.kanade.tachiyomi.util.preference.onChange
import eu.kanade.tachiyomi.util.preference.switchPreferenceCategory import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import java.util.TreeMap import java.util.TreeMap
@ -33,28 +29,22 @@ class SettingsSourcesController : SettingsController() {
val orderedLangs = sourcesByLang.keys.sortedWith(compareBy({ it !in activeLangsCodes }, { LocaleHelper.getSourceDisplayName(it, context) })) val orderedLangs = sourcesByLang.keys.sortedWith(compareBy({ it !in activeLangsCodes }, { LocaleHelper.getSourceDisplayName(it, context) }))
orderedLangs.forEach { lang -> orderedLangs.forEach { lang ->
val sources = sourcesByLang[lang].orEmpty().sortedBy { it.name } switchPreference {
// Create a preference group and set initial state and change listener
switchPreferenceCategory {
preferenceScreen.addPreference(this) preferenceScreen.addPreference(this)
title = LocaleHelper.getSourceDisplayName(lang, context) title = LocaleHelper.getSourceDisplayName(lang, context)
isPersistent = false isPersistent = false
if (lang in activeLangsCodes) { isChecked = lang in activeLangsCodes
setChecked(true)
addLanguageSources(this, sources)
}
onChange { newValue -> onChange { newValue ->
val checked = newValue as Boolean val checked = newValue as Boolean
val current = preferences.enabledLanguages().get() val current = preferences.enabledLanguages().get()
if (!checked) { preferences.enabledLanguages().set(
preferences.enabledLanguages().set(current - lang) if (!checked) {
removeAll() current - lang
} else { } else {
preferences.enabledLanguages().set(current + lang) current + lang
addLanguageSources(this, sources) }
} )
true true
} }
} }
@ -64,49 +54,4 @@ class SettingsSourcesController : SettingsController() {
override fun setDivider(divider: Drawable?) { override fun setDivider(divider: Drawable?) {
super.setDivider(null) super.setDivider(null)
} }
/**
* Adds the source list for the given group (language).
*
* @param group the language category.
*/
private fun addLanguageSources(group: PreferenceGroup, sources: List<HttpSource>) {
val hiddenCatalogues = preferences.hiddenCatalogues().get()
sources.forEach { source ->
val sourcePreference = CheckBoxPreference(group.context).apply {
val id = source.id.toString()
title = source.name
key = getSourceKey(source.id)
isPersistent = false
isChecked = id !in hiddenCatalogues
val sourceIcon = source.icon()
if (sourceIcon != null) {
icon = sourceIcon
}
onChange { newValue ->
val checked = newValue as Boolean
val current = preferences.hiddenCatalogues().get()
preferences.hiddenCatalogues().set(
if (checked) {
current - id
} else {
current + id
}
)
true
}
}
group.addPreference(sourcePreference)
}
}
private fun getSourceKey(sourceId: Long): String {
return "source_$sourceId"
}
} }

View File

@ -88,19 +88,14 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/extension_lang" /> app:layout_constraintTop_toBottomOf="@id/extension_lang" />
<TextView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/extension_prefs" android:id="@+id/extension_prefs_recycler"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="@dimen/material_component_lists_two_line_height" android:layout_height="0dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:background="@drawable/list_item_selector" app:layout_constraintBottom_toBottomOf="parent"
android:gravity="center_vertical"
android:padding="16dp"
android:text="@string/label_settings"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/extension_uninstall_button" app:layout_constraintTop_toBottomOf="@id/extension_uninstall_button" />
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/extension_prefs_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/extension_prefs_empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>

View File

@ -0,0 +1,14 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_enable_all"
android:title="@string/action_enable_all"
app:showAsAction="never" />
<item
android:id="@+id/action_disable_all"
android:title="@string/action_disable_all"
app:showAsAction="never" />
</menu>

View File

@ -55,6 +55,8 @@
<string name="action_delete">Delete</string> <string name="action_delete">Delete</string>
<string name="action_update">Update</string> <string name="action_update">Update</string>
<string name="action_update_library">Update library</string> <string name="action_update_library">Update library</string>
<string name="action_enable_all">Enable all</string>
<string name="action_disable_all">Disable all</string>
<string name="action_edit">Edit</string> <string name="action_edit">Edit</string>
<string name="action_add">Add</string> <string name="action_add">Add</string>
<string name="action_add_category">Add category</string> <string name="action_add_category">Add category</string>
@ -222,7 +224,6 @@
<string name="unofficial_extension_message">This extension is not from the official Tachiyomi extensions list.</string> <string name="unofficial_extension_message">This extension is not from the official Tachiyomi extensions list.</string>
<string name="ext_version_info">Version: %1$s</string> <string name="ext_version_info">Version: %1$s</string>
<string name="ext_language_info">Language: %1$s</string> <string name="ext_language_info">Language: %1$s</string>
<string name="ext_empty_preferences">No preferences to edit for this extension</string>
<!-- Reader section --> <!-- Reader section -->
<string name="pref_fullscreen">Fullscreen</string> <string name="pref_fullscreen">Fullscreen</string>