Move source preferences to separate controller

This commit is contained in:
arkon 2020-06-05 09:50:30 -04:00
parent ec7297f8c2
commit 812fcec9f0
5 changed files with 218 additions and 86 deletions

View File

@ -11,13 +11,6 @@ import android.view.MenuItem
import android.view.View
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
@ -28,7 +21,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
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.extension.model.Extension
import eu.kanade.tachiyomi.source.CatalogueSource
@ -36,10 +28,13 @@ import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.util.preference.checkBoxPreference
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.util.preference.onChange
import eu.kanade.tachiyomi.util.preference.onClick
import eu.kanade.tachiyomi.util.preference.preference
import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.view.visible
import kotlinx.coroutines.flow.launchIn
@ -50,14 +45,11 @@ import uy.kohesive.injekt.injectLazy
@SuppressLint("RestrictedApi")
class ExtensionDetailsController(bundle: Bundle? = null) :
NucleusController<ExtensionDetailControllerBinding, ExtensionDetailsPresenter>(bundle),
NoToolbarElevationController,
PreferenceManager.OnDisplayPreferenceDialogListener,
DialogPreference.TargetFragment {
NoToolbarElevationController {
private val preferences: PreferencesHelper by injectLazy()
private var preferenceScreen: PreferenceScreen? = null
private var lastOpenPreferencePosition: Int? = null
constructor(pkgName: String) : this(
Bundle().apply {
@ -114,29 +106,37 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
}
private fun initPreferences(context: Context, extension: Extension.Installed) {
val themedContext by lazy { getPreferenceThemeContext() }
val themedContext = getPreferenceThemeContext()
val manager = PreferenceManager(themedContext)
manager.preferenceDataStore = EmptyPreferenceDataStore()
manager.onDisplayPreferenceDialogListener = this
val screen = manager.createPreferenceScreen(themedContext)
preferenceScreen = screen
val isMultiSource = extension.sources.size > 1
with(screen) {
extension.sources
.groupBy { (it as CatalogueSource).lang }
.toSortedMap(compareBy { LocaleHelper.getSourceDisplayName(it, context) })
.forEach {
preferenceCategory {
title = LocaleHelper.getSourceDisplayName(it.key, context)
if (isMultiSource) {
title = LocaleHelper.getSourceDisplayName(it.key, context)
}
it.value
.sortedWith(compareBy({ !it.isEnabled() }, { it.name }))
.forEach { source ->
val sourcePrefs = mutableListOf<Preference>()
// Source enable/disable
checkBoxPreference {
switchPreference {
key = getSourceKey(source.id)
title = source.toString()
title = if (isMultiSource) {
source.toString()
} else {
context.getString(R.string.enabled)
}
isPersistent = false
isChecked = source.isEnabled()
@ -158,28 +158,11 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
// 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)
preference {
titleRes = R.string.label_settings
onClick {
router.pushController(SourcePreferencesController(source.id).withFadeTransaction())
}
}
}
}
@ -192,16 +175,6 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
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)
@ -253,43 +226,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
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

@ -0,0 +1,175 @@
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.SourcePreferencesControllerBinding
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import timber.log.Timber
@SuppressLint("RestrictedApi")
class SourcePreferencesController(bundle: Bundle? = null) :
NucleusController<SourcePreferencesControllerBinding, SourcePreferencesPresenter>(bundle),
PreferenceManager.OnDisplayPreferenceDialogListener,
DialogPreference.TargetFragment {
private var lastOpenPreferencePosition: Int? = null
private var preferenceScreen: PreferenceScreen? = null
constructor(sourceId: Long) : this(
Bundle().apply {
putLong(SOURCE_ID, sourceId)
}
)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
binding = SourcePreferencesControllerBinding.inflate(themedInflater)
return binding.root
}
override fun createPresenter(): SourcePreferencesPresenter {
return SourcePreferencesPresenter(args.getLong(SOURCE_ID))
}
override fun getTitle(): String? {
return presenter.source?.toString()
}
@SuppressLint("PrivateResource")
override fun onViewCreated(view: View) {
super.onViewCreated(view)
val source = presenter.source ?: 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
try {
addPreferencesForSource(screen, source)
} catch (e: AbstractMethodError) {
Timber.e("Source did not implement [addPreferencesForSource]: ${source.name}")
}
manager.setPreferences(screen)
binding.recycler.layoutManager = LinearLayoutManager(context)
binding.recycler.adapter = PreferenceGroupAdapter(screen)
binding.recycler.addItemDecoration(DividerItemDecoration(context, VERTICAL))
}
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) {
val context = screen.context
val dataStore = SharedPreferencesDataStore(
context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE)
)
if (source is ConfigurableSource) {
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 SOURCE_ID = "source_id"
const val LASTOPENPREFERENCE_KEY = "last_open_preference"
}
}

View File

@ -0,0 +1,14 @@
package eu.kanade.tachiyomi.ui.browse.extension
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class SourcePreferencesPresenter(
val sourceId: Long,
sourceManager: SourceManager = Injekt.get()
) : BasePresenter<SourcePreferencesController>() {
val source = sourceManager.get(sourceId)
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@ -224,6 +224,7 @@
<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_language_info">Language: %1$s</string>
<string name="enabled">Enabled</string>
<!-- Reader section -->
<string name="pref_fullscreen">Fullscreen</string>