Manage sources from extension details (closes #3152)

(cherry picked from commit 54cfb2acdfbdb29156e8053bdbd3e2fdfe3f6a43)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionDetailsController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt
This commit is contained in:
arkon 2020-05-31 16:23:51 -04:00 committed by Jobobby04
parent 7d4af98cfb
commit f5084f96d9
8 changed files with 243 additions and 257 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
} }
@ -70,25 +113,186 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
binding.extensionWarningBanner.setText(R.string.redundant_extension_message) binding.extensionWarningBanner.setText(R.string.redundant_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

@ -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>