Updated Extensions Settings view

Some changes from upstream plus:
Settings in same view (collapsible)
Sorted by enabled languages
Disabled languages show as off
Disabled sources due to language can be turned on via tapping on them then the snackbar
This commit is contained in:
Jays2Kings 2021-03-23 20:13:17 -04:00
parent 5afb2461e5
commit 9a220073b9
27 changed files with 491 additions and 215 deletions

View File

@ -98,7 +98,7 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.2.0")
implementation("androidx.cardview:cardview:1.0.0")
implementation("com.google.android.material:material:1.3.0")
implementation("androidx.recyclerview:recyclerview:1.1.0")
implementation("androidx.recyclerview:recyclerview:1.2.0-beta02")
implementation("androidx.preference:preference:1.1.1")
implementation("androidx.annotation:annotation:1.1.0")
implementation("androidx.browser:browser:1.3.0")

View File

@ -43,6 +43,14 @@ private class DateFormatConverter : Preference.Adapter<DateFormat> {
}
}
operator fun <T> com.tfcporciuncula.flow.Preference<Set<T>>.plusAssign(item: T) {
set(get() + item)
}
operator fun <T> com.tfcporciuncula.flow.Preference<Set<T>>.minusAssign(item: T) {
set(get() - item)
}
class PreferencesHelper(val context: Context) {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
@ -135,7 +143,7 @@ class PreferencesHelper(val context: Context) {
fun browseAsList() = rxPrefs.getBoolean(Keys.catalogueAsList, false)
fun enabledLanguages() = rxPrefs.getStringSet(Keys.enabledLanguages, setOf("en", Locale.getDefault().language))
fun enabledLanguages() = flowPrefs.getStringSet(Keys.enabledLanguages, setOf("en", Locale.getDefault().language))
fun sourceSorting() = rxPrefs.getInteger(Keys.sourcesSort, 0)
@ -211,7 +219,7 @@ class PreferencesHelper(val context: Context) {
fun collapsedCategories() = rxPrefs.getStringSet("collapsed_categories", mutableSetOf())
fun hiddenSources() = rxPrefs.getStringSet("hidden_catalogues", mutableSetOf())
fun hiddenSources() = flowPrefs.getStringSet("hidden_catalogues", mutableSetOf())
fun pinnedCatalogues() = rxPrefs.getStringSet("pinned_catalogues", emptySet())

View File

@ -97,3 +97,5 @@ interface Source : tachiyomi.source.Source {
fun Source.icon(): Drawable? =
Injekt.get<ExtensionManager>().getAppIconForSource(this)
fun Source.getPreferenceKey(): String = "source_$id"

View File

@ -146,7 +146,7 @@ class ExtensionBottomPresenter(
@Synchronized
private fun toItems(tuple: ExtensionTuple): List<ExtensionItem> {
val context = bottomSheet.context
val activeLangs = preferences.enabledLanguages().getOrDefault()
val activeLangs = preferences.enabledLanguages().get()
val (installed, untrusted, available) = tuple
@ -174,7 +174,7 @@ class ExtensionBottomPresenter(
}
if (availableSorted.isNotEmpty()) {
val availableGroupedByLang = availableSorted
.groupBy { LocaleHelper.getDisplayName(it.lang, context) }
.groupBy { LocaleHelper.getSourceDisplayName(it.lang, context) }
.toSortedMap()
availableGroupedByLang

View File

@ -2,38 +2,30 @@ package eu.kanade.tachiyomi.ui.extension
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.core.view.get
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.ui.extension.details.ExtensionDetailsController
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.migration.MangaAdapter
import eu.kanade.tachiyomi.ui.migration.MangaItem
import eu.kanade.tachiyomi.ui.migration.SourceAdapter
import eu.kanade.tachiyomi.ui.migration.SourceItem
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.ui.recents.RecentMangaHolder
import eu.kanade.tachiyomi.ui.source.SourceController
import eu.kanade.tachiyomi.util.system.await
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.expand
import eu.kanade.tachiyomi.util.view.isExpanded
import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.withFadeTransaction
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
@ -42,9 +34,6 @@ import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.migration_controller.*
import kotlinx.android.synthetic.main.recents_controller.*
import kotlinx.android.synthetic.main.recycler_with_scroller.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -244,9 +233,11 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
.showDialog(controller.router)
}
fun setExtensions(extensions: List<ExtensionItem>) {
fun setExtensions(extensions: List<ExtensionItem>, updateController: Boolean = true) {
this.extensions = extensions
if (updateController) {
controller.presenter.updateSources()
}
drawExtensions()
}

View File

@ -40,7 +40,7 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
// Set source name
ext_title.text = extension.name
version.text = extension.versionName
lang.text = LocaleHelper.getDisplayName(extension.lang, itemView.context)
lang.text = LocaleHelper.getDisplayName(extension.lang)
warning.text = when {
extension is Extension.Untrusted -> itemView.context.getString(R.string.untrusted)
extension is Extension.Installed && extension.isObsolete -> itemView.context.getString(R.string.obsolete)

View File

@ -17,7 +17,7 @@ class SettingsExtensionsController : SettingsController() {
override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
titleRes = R.string.filter
val activeLangs = preferences.enabledLanguages().getOrDefault()
val activeLangs = preferences.enabledLanguages().get()
val availableLangs =
Injekt.get<ExtensionManager>().availableExtensions.groupBy {
@ -31,13 +31,13 @@ class SettingsExtensionsController : SettingsController() {
availableLangs.forEach {
SwitchPreference(context).apply {
preferenceScreen.addPreference(this)
title = LocaleHelper.getDisplayName(it, context)
title = LocaleHelper.getSourceDisplayName(it, context)
isPersistent = false
isChecked = it in activeLangs
onChange { newValue ->
val checked = newValue as Boolean
val currentActiveLangs = preferences.enabledLanguages().getOrDefault()
val currentActiveLangs = preferences.enabledLanguages().get()
if (checked) {
preferences.enabledLanguages().set(currentActiveLangs + it)

View File

@ -1,11 +1,17 @@
package eu.kanade.tachiyomi.ui.extension
package eu.kanade.tachiyomi.ui.extension.details
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.preference.DialogPreference
@ -19,20 +25,33 @@ import androidx.preference.Preference
import androidx.preference.PreferenceGroupAdapter
import androidx.preference.PreferenceManager
import androidx.preference.PreferenceScreen
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
import com.jakewharton.rxbinding.view.clicks
import androidx.preference.SwitchPreferenceCompat
import androidx.recyclerview.widget.ConcatAdapter
import com.google.android.material.snackbar.Snackbar
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.data.preference.minusAssign
import eu.kanade.tachiyomi.data.preference.plusAssign
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.getPreferenceKey
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.setting.preferenceCategory
import eu.kanade.tachiyomi.ui.setting.DSL
import eu.kanade.tachiyomi.ui.setting.onChange
import eu.kanade.tachiyomi.ui.setting.switchPreference
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController
import eu.kanade.tachiyomi.util.view.openInBrowser
import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.widget.preference.ListMatPreference
import kotlinx.android.synthetic.main.extension_detail_controller.*
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@SuppressLint("RestrictedApi")
class ExtensionDetailsController(bundle: Bundle? = null) :
@ -44,6 +63,13 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
private var preferenceScreen: PreferenceScreen? = null
private val preferences: PreferencesHelper = Injekt.get()
private val viewScope = MainScope()
init {
setHasOptionsMenu(true)
}
constructor(pkgName: String) : this(
Bundle().apply {
putString(PKGNAME_KEY, pkgName)
@ -67,24 +93,11 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
@SuppressLint("PrivateResource")
override fun onViewCreated(view: View) {
super.onViewCreated(view)
view.applyWindowInsetsForController()
scrollViewWith(extension_prefs_recycler, padBottom = true)
val extension = presenter.extension ?: return
val context = view.context
extension_title.text = extension.name
extension_version.text = context.getString(R.string.version_, extension.versionName)
extension_lang.text = context.getString(R.string.language_, LocaleHelper.getDisplayName(extension.lang, context))
extension_pkg.text = extension.pkgName
extension.getApplicationIcon(context)?.let { extension_icon.setImageDrawable(it) }
extension_uninstall_button.clicks().subscribeUntilDestroy {
presenter.uninstallExtension()
}
if (extension.isObsolete) {
extension_obsolete.visibility = View.VISIBLE
}
val themedContext by lazy { getPreferenceThemeContext() }
val manager = PreferenceManager(themedContext)
manager.preferenceDataStore = EmptyPreferenceDataStore()
@ -93,10 +106,12 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
preferenceScreen = screen
val multiSource = extension.sources.size > 1
val isMultiLangSingleSource = multiSource && extension.sources.map { it.name }.distinct().size == 1
val langauges = preferences.enabledLanguages().get()
for (source in extension.sources) {
for (source in extension.sources.sortedByDescending { it.isLangEnabled(langauges) }) {
if (source is ConfigurableSource) {
addPreferencesForSource(screen, source, multiSource)
addPreferencesForSource(screen, source, multiSource, isMultiLangSingleSource)
}
}
@ -104,16 +119,17 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
extension_prefs_recycler.layoutManager =
androidx.recyclerview.widget.LinearLayoutManager(context)
extension_prefs_recycler.adapter = PreferenceGroupAdapter(screen)
extension_prefs_recycler.addItemDecoration(androidx.recyclerview.widget.DividerItemDecoration(context, VERTICAL))
extension_prefs_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
if (screen.preferenceCount == 0) {
extension_prefs_empty_view.show(
R.drawable.ic_no_settings_24dp,
R.string.empty_preferences_for_extension
val concatAdapterConfig = ConcatAdapter.Config.Builder()
.setStableIdMode(ConcatAdapter.Config.StableIdMode.ISOLATED_STABLE_IDS)
.build()
screen.setShouldUseGeneratedIds(true)
val extHeaderAdapter = ExtensionDetailsHeaderAdapter(presenter)
extHeaderAdapter.setHasStableIds(true)
extension_prefs_recycler.adapter = ConcatAdapter(concatAdapterConfig,
extHeaderAdapter,
PreferenceGroupAdapter(screen)
)
}
extension_prefs_recycler.addItemDecoration(ExtensionSettingsDividerItemDecoration(context))
}
override fun onDestroyView(view: View) {
@ -135,41 +151,113 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
lastOpenPreferencePosition = savedInstanceState.get(LASTOPENPREFERENCE_KEY) as? Int
}
private fun addPreferencesForSource(screen: PreferenceScreen, source: Source, multiSource: Boolean) {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.extension_details, menu)
menu.findItem(R.id.action_history).isVisible = presenter.extension?.isUnofficial == false
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_history -> openCommitHistory()
R.id.action_app_info -> openInSettings()
}
return super.onOptionsItemSelected(item)
}
private fun openCommitHistory() {
val pkgName = presenter.extension!!.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
val pkgFactory = presenter.extension!!.pkgFactory
val url = when {
!pkgFactory.isNullOrEmpty() -> "$URL_EXTENSION_COMMITS/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/$pkgFactory"
else -> "$URL_EXTENSION_COMMITS/src/${pkgName.replace(".", "/")}"
}
openInBrowser(url)
}
private fun openInSettings() {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", presenter.pkgName, null)
}
startActivity(intent)
}
private fun addPreferencesForSource(screen: PreferenceScreen, source: Source, isMultiSource: Boolean, isMultiLangSingleSource: Boolean) {
val context = screen.context
// TODO
val dataStore = SharedPreferencesDataStore(/*if (source is HttpSource) {
source.preferences
} else {*/
val dataStore = SharedPreferencesDataStore(
context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE)
/*}*/
)
if (source is ConfigurableSource) {
if (multiSource) {
screen.preferenceCategory {
title = source.toString()
val prefs = mutableListOf<Preference>()
val block: (@DSL SwitchPreferenceCompat).() -> Unit = {
key = source.getPreferenceKey()
title = when {
isMultiSource && !isMultiLangSingleSource -> source.toString()
else -> LocaleHelper.getSourceDisplayName(source.lang, context)
}
isPersistent = false
isChecked = source.isEnabled()
onChange { newValue ->
if (source.isLangEnabled()) {
val checked = newValue as Boolean
toggleSource(source, checked)
prefs.forEach { it.isVisible = checked }
true
}
else {
coordinator.snack(context.getString(R.string._must_be_enabled_first, title), Snackbar.LENGTH_LONG) {
setAction(R.string.enable) {
preferences.enabledLanguages() += source.lang
isChecked = true
toggleSource(source, true)
prefs.forEach { it.isVisible = true }
}
}
false
}
}
// React to enable/disable all changes
preferences.hiddenSources().asFlow()
.onEach {
val enabled = source.isEnabled()
isChecked = enabled
}
.launchIn(viewScope)
}
val newScreen = screen.preferenceManager.createPreferenceScreen(context)
screen.switchPreference(block)
source.setupPreferenceScreen(newScreen)
// Reparent the preferences
while (newScreen.preferenceCount != 0) {
val pref = newScreen.getPreference(0)
pref.isIconSpaceReserved = false
pref.isIconSpaceReserved = true
pref.preferenceDataStore = dataStore
pref.fragment = "source_${source.id}"
pref.order = Int.MAX_VALUE // reset to default order
pref.order = Int.MAX_VALUE
pref.isVisible = source.isEnabled()
prefs.add(pref)
newScreen.removePreference(pref)
screen.addPreference(pref)
}
}
}
private fun toggleSource(source: Source, enable: Boolean) {
if (enable) {
preferences.hiddenSources() -= source.id.toString()
} else {
preferences.hiddenSources() += source.id.toString()
}
}
private fun getPreferenceThemeContext(): Context {
val tv = TypedValue()
activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
@ -216,6 +304,15 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
f.showDialog(router)
}
private fun Source.isEnabled(): Boolean {
return id.toString() !in preferences.hiddenSources().get() && isLangEnabled()
}
private fun Source.isLangEnabled(langs: Set<String>? = null): Boolean {
return (lang in langs ?: preferences.enabledLanguages().get())
}
@Suppress("UNCHECKED_CAST")
override fun <T : Preference> findPreference(key: CharSequence): T? {
// We track [lastOpenPreferencePosition] when displaying the dialog
@ -226,5 +323,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
private companion object {
const val PKGNAME_KEY = "pkg_name"
const val LASTOPENPREFERENCE_KEY = "last_open_preference"
private const val URL_EXTENSION_COMMITS =
"https://github.com/tachiyomiorg/tachiyomi-extensions/commits/master"
}
}

View File

@ -0,0 +1,62 @@
package eu.kanade.tachiyomi.ui.extension.details
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.extension.getApplicationIcon
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.view.inflate
import kotlinx.android.synthetic.main.extension_detail_header.view.*
class ExtensionDetailsHeaderAdapter(private val presenter: ExtensionDetailsPresenter) :
RecyclerView.Adapter<ExtensionDetailsHeaderAdapter.HeaderViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
val view = parent.inflate(R.layout.extension_detail_header)
return HeaderViewHolder(view)
}
override fun getItemCount(): Int = 1
override fun onBindViewHolder(holder: HeaderViewHolder, position: Int) {
holder.bind()
}
override fun getItemViewType(position: Int): Int {
return R.layout.extension_detail_header
}
override fun getItemId(position: Int): Long {
return presenter.pkgName.hashCode().toLong()
}
inner class HeaderViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
fun bind() {
val extension = presenter.extension ?: return
val context = view.context
extension.getApplicationIcon(context)?.let { view.extension_icon.setImageDrawable(it) }
view.extension_title.text = extension.name
view.extension_version.text = context.getString(R.string.version_, extension.versionName)
view.extension_lang.text = context.getString(R.string.language_, LocaleHelper.getSourceDisplayName(extension.lang, context))
view.extension_nsfw.isVisible = extension.isNsfw
view.extension_pkg.text = extension.pkgName
view.extension_uninstall_button.setOnClickListener {
presenter.uninstallExtension()
}
if (extension.isObsolete) {
view.extension_warning_banner.isVisible = true
view.extension_warning_banner.setText(R.string.obsolete_extension_message)
}
if (extension.isUnofficial) {
view.extension_warning_banner.isVisible = true
view.extension_warning_banner.setText(R.string.unofficial_extension_message)
}
}
}
}

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.ui.extension
package eu.kanade.tachiyomi.ui.extension.details
import android.os.Bundle
import eu.kanade.tachiyomi.extension.ExtensionManager

View File

@ -0,0 +1,54 @@
package eu.kanade.tachiyomi.ui.extension.details
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.view.View
import androidx.preference.PreferenceGroupAdapter
import androidx.preference.SwitchPreferenceCompat
import androidx.recyclerview.widget.ConcatAdapter
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.isLTR
class ExtensionSettingsDividerItemDecoration(context: Context) : androidx.recyclerview.widget.RecyclerView.ItemDecoration() {
private val divider: Drawable
init {
val a = context.obtainStyledAttributes(intArrayOf(android.R.attr.listDivider))
divider = a.getDrawable(0)!!
a.recycle()
}
@SuppressLint("RestrictedApi")
override fun onDraw(c: Canvas, parent: androidx.recyclerview.widget.RecyclerView, state: androidx.recyclerview.widget.RecyclerView.State) {
val childCount = parent.childCount
for (i in 0 until childCount - 1) {
val child = parent.getChildAt(i)
val index = parent.getChildAdapterPosition(child)
val adapter = (parent.adapter as? ConcatAdapter)?.adapters?.lastOrNull() as? PreferenceGroupAdapter
if (index > 0 && adapter?.getItem(index) is SwitchPreferenceCompat) {
val params = child.layoutParams as androidx.recyclerview.widget.RecyclerView.LayoutParams
val top = child.bottom + params.bottomMargin
val bottom = top + divider.intrinsicHeight
val left = parent.paddingStart + if (parent.context.resources.isLTR) 12.dpToPx else 0
val right =
parent.width - parent.paddingEnd - if (!parent.context.resources.isLTR) 12.dpToPx else 0
divider.setBounds(left, top, right, bottom)
divider.draw(c)
}
}
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: androidx.recyclerview.widget.RecyclerView,
state: androidx.recyclerview.widget.RecyclerView.State
) {
outRect.set(0, 0, 0, divider.intrinsicHeight)
}
}

View File

@ -18,7 +18,7 @@ class MigrationSourceHolder(view: View, val adapter: MigrationSourceAdapter) :
fun bind(source: HttpSource, sourceEnabled: Boolean) {
val preferences by injectLazy<PreferencesHelper>()
val isMultiLanguage = preferences.enabledLanguages().getOrDefault().size > 1
val isMultiLanguage = preferences.enabledLanguages().get().size > 1
// Set capitalized title.
val sourceName = if (isMultiLanguage) source.toString() else source.name.capitalize()
title.text = sourceName

View File

@ -142,7 +142,7 @@ class PreMigrationController(bundle: Bundle? = null) :
* @return list containing enabled sources.
*/
private fun getEnabledSources(): List<HttpSource> {
val languages = prefs.enabledLanguages().getOrDefault()
val languages = prefs.enabledLanguages().get()
val sourcesSaved = prefs.migrationSources().get().split("/")
var sources = sourceManager.getCatalogueSources()
.filterIsInstance<HttpSource>()
@ -162,7 +162,7 @@ class PreMigrationController(bundle: Bundle? = null) :
fun isEnabled(id: String): Boolean {
val sourcesSaved = prefs.migrationSources().get()
val hiddenCatalogues = prefs.hiddenSources().getOrDefault()
val hiddenCatalogues = prefs.hiddenSources().get()
return if (sourcesSaved.isEmpty()) id !in hiddenCatalogues
else sourcesSaved.split("/").contains(id)
}
@ -181,7 +181,7 @@ class PreMigrationController(bundle: Bundle? = null) :
}
R.id.action_match_enabled, R.id.action_match_pinned -> {
val enabledSources = if (item.itemId == R.id.action_match_enabled) {
prefs.hiddenSources().getOrDefault().mapNotNull { it.toLongOrNull() }
prefs.hiddenSources().get().mapNotNull { it.toLongOrNull() }
} else {
prefs.pinnedCatalogues().get()?.mapNotNull { it.toLongOrNull() } ?: emptyList()
}

View File

@ -85,7 +85,7 @@ class AboutController : SettingsController() {
}
preferenceCategory {
preference {
titleRes = R.string.whats_new
titleRes = R.string.whats_new_this_release
onClick {
val intent = Intent(
Intent.ACTION_VIEW,

View File

@ -89,6 +89,10 @@ inline fun PreferenceScreen.preferenceCategory(block: (@DSL PreferenceCategory).
)
}
inline fun PreferenceScreen.switchPreference(block: (@DSL SwitchPreferenceCompat).() -> Unit): SwitchPreferenceCompat {
return initThenAdd(SwitchPreferenceCompat(context), block)
}
inline fun PreferenceGroup.infoPreference(@StringRes infoRes: Int): Preference {
return initThenAdd(
Preference(context),

View File

@ -88,8 +88,8 @@ class SettingsBrowseController : SettingsController() {
summaryRes = R.string.only_enable_enabled_for_migration
onClick {
val ogSources = preferences.migrationSources().get()
val languages = preferences.enabledLanguages().getOrDefault()
val hiddenCatalogues = preferences.hiddenSources().getOrDefault()
val languages = preferences.enabledLanguages().get()
val hiddenCatalogues = preferences.hiddenSources().get()
val enabledSources =
sourceManager.getCatalogueSources().filter { it.lang in languages }
.filterNot { it.id.toString() in hiddenCatalogues }

View File

@ -11,6 +11,8 @@ import androidx.preference.PreferenceScreen
import com.jakewharton.rxbinding.support.v7.widget.queryTextChanges
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.preference.minusAssign
import eu.kanade.tachiyomi.data.preference.plusAssign
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.icon
import eu.kanade.tachiyomi.source.online.HttpSource
@ -39,7 +41,7 @@ class SettingsSourcesController : SettingsController() {
sorting = SourcesSort.from(preferences.sourceSorting().getOrDefault()) ?: SourcesSort.Alpha
activity?.invalidateOptionsMenu()
// Get the list of active language codes.
val activeLangsCodes = preferences.enabledLanguages().getOrDefault()
val activeLangsCodes = preferences.enabledLanguages().get()
// Get a map of sources grouped by language.
sourcesByLang = onlineSources.groupByTo(TreeMap(), { it.lang })
@ -55,7 +57,7 @@ class SettingsSourcesController : SettingsController() {
lang,
SwitchPreferenceCategory(context).apply {
preferenceScreen.addPreference(this)
title = LocaleHelper.getDisplayName(lang, context)
title = LocaleHelper.getSourceDisplayName(lang, context)
isPersistent = false
if (lang in activeLangsCodes) {
setChecked(true)
@ -64,12 +66,12 @@ class SettingsSourcesController : SettingsController() {
onChange { newValue ->
val checked = newValue as Boolean
val current = preferences.enabledLanguages().getOrDefault()
val current = preferences.enabledLanguages().get()
if (!checked) {
preferences.enabledLanguages().set(current - lang)
preferences.enabledLanguages() -= lang
removeAll()
} else {
preferences.enabledLanguages().set(current + lang)
preferences.enabledLanguages() += lang
addLanguageSources(this, sortedSources(sourcesByLang[lang]))
}
true
@ -90,7 +92,7 @@ class SettingsSourcesController : SettingsController() {
* @param group the language category.
*/
private fun addLanguageSources(group: PreferenceGroup, sources: List<HttpSource>) {
val hiddenCatalogues = preferences.hiddenSources().getOrDefault()
val hiddenCatalogues = preferences.hiddenSources().get()
val selectAllPreference = CheckBoxPreference(group.context).apply {
@ -102,7 +104,7 @@ class SettingsSourcesController : SettingsController() {
onChange { newValue ->
val checked = newValue as Boolean
val current = preferences.hiddenSources().get() ?: mutableSetOf()
val current = preferences.hiddenSources().get().toMutableSet()
if (checked)
current.removeAll(sources.map { it.id.toString() })
else
@ -131,7 +133,7 @@ class SettingsSourcesController : SettingsController() {
onChange { newValue ->
val checked = newValue as Boolean
val current = preferences.hiddenSources().getOrDefault()
val current = preferences.hiddenSources().get()
preferences.hiddenSources().set(
if (checked) current - id
@ -215,7 +217,7 @@ class SettingsSourcesController : SettingsController() {
}
private fun drawSources() {
val activeLangsCodes = preferences.enabledLanguages().getOrDefault()
val activeLangsCodes = preferences.enabledLanguages().get()
langPrefs.forEach { group ->
if (group.first in activeLangsCodes) {
group.second.removeAll()
@ -227,7 +229,7 @@ class SettingsSourcesController : SettingsController() {
private fun sortedSources(sources: List<HttpSource>?): List<HttpSource> {
val sourceAlpha = sources.orEmpty().sortedBy { it.name }
return if (sorting == SourcesSort.Enabled) {
val hiddenCatalogues = preferences.hiddenSources().getOrDefault()
val hiddenCatalogues = preferences.hiddenSources().get()
sourceAlpha.filter { it.id.toString() !in hiddenCatalogues } +
sourceAlpha.filterNot { it.id.toString() !in hiddenCatalogues }
} else {

View File

@ -12,6 +12,6 @@ class LangHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.Vie
BaseFlexibleViewHolder(view, adapter) {
fun bind(item: LangItem) {
title.text = LocaleHelper.getDisplayName(item.code, itemView.context)
title.text = LocaleHelper.getDisplayName(item.code)
}
}

View File

@ -315,9 +315,10 @@ class SourceController :
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (!type.isPush && handler is SettingsSourcesFadeChangeHandler) {
if (!type.isPush) {
ext_bottom_sheet.updateExtTitle()
ext_bottom_sheet.presenter.refreshExtensions()
presenter.updateSources()
}
if (!type.isEnter) {
ext_bottom_sheet.canExpand = false
@ -358,7 +359,7 @@ class SourceController :
fun hideCatalogue(position: Int) {
val source = (adapter?.getItem(position) as? SourceItem)?.source ?: return
val current = preferences.hiddenSources().getOrDefault()
val current = preferences.hiddenSources().get()
preferences.hiddenSources().set(current + source.id.toString())
presenter.updateSources()
@ -366,7 +367,7 @@ class SourceController :
snackbar = view?.snack(R.string.source_hidden, Snackbar.LENGTH_INDEFINITE) {
anchorView = ext_bottom_sheet
setAction(R.string.undo) {
val newCurrent = preferences.hiddenSources().getOrDefault()
val newCurrent = preferences.hiddenSources().get()
preferences.hiddenSources().set(newCurrent - source.id.toString())
presenter.updateSources()
}

View File

@ -111,8 +111,8 @@ class SourcePresenter(
* @return list containing enabled sources.
*/
private fun getEnabledSources(): List<CatalogueSource> {
val languages = preferences.enabledLanguages().getOrDefault()
val hiddenCatalogues = preferences.hiddenSources().getOrDefault()
val languages = preferences.enabledLanguages().get()
val hiddenCatalogues = preferences.hiddenSources().get()
return sourceManager.getCatalogueSources()
.filter { it.lang in languages }

View File

@ -101,8 +101,8 @@ open class GlobalSearchPresenter(
* @return list containing enabled sources.
*/
protected open fun getEnabledSources(): List<CatalogueSource> {
val languages = preferencesHelper.enabledLanguages().getOrDefault()
val hiddenCatalogues = preferencesHelper.hiddenSources().getOrDefault()
val languages = preferencesHelper.enabledLanguages().get()
val hiddenCatalogues = preferencesHelper.hiddenSources().get()
val pinnedCatalogues = preferencesHelper.pinnedCatalogues().getOrDefault()
val list = sourceManager.getCatalogueSources()

View File

@ -53,13 +53,25 @@ object LocaleHelper {
/**
* Returns Display name of a string language code
*/
fun getDisplayName(lang: String?, context: Context): String {
fun getSourceDisplayName(lang: String?, context: Context): String {
return when (lang) {
"" -> context.getString(R.string.other)
SourcePresenter.LAST_USED_KEY -> context.getString(R.string.last_used)
SourcePresenter.PINNED_KEY -> context.getString(R.string.pinned)
"all" -> context.getString(R.string.all)
else -> getDisplayName(lang)
}
}
/**
* Returns Display name of a string language code
*/
fun getDisplayName(lang: String?): String {
return when (lang) {
null -> ""
"" -> context.getString(R.string.other)
SourcePresenter.PINNED_KEY -> context.getString(R.string.pinned)
SourcePresenter.LAST_USED_KEY -> context.getString(R.string.last_used)
"all" -> context.getString(R.string.all)
"" -> {
systemLocale!!.getDisplayName(systemLocale).capitalize()
}
else -> {
val locale = getLocale(lang)
locale.getDisplayName(locale).capitalize()

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.util.view
import android.animation.ValueAnimator
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.view.View
import android.view.ViewGroup
@ -11,6 +12,7 @@ import android.view.inputmethod.InputMethodManager
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.math.MathUtils
import androidx.core.net.toUri
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.bluelinelabs.conductor.Controller
@ -24,6 +26,7 @@ import eu.kanade.tachiyomi.ui.main.BottomSheetController
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.android.synthetic.main.main_activity.*
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -120,6 +123,11 @@ fun Controller.scrollViewWith(
val randomTag = Random.nextLong()
var lastY = 0f
var fakeToolbarView: View? = null
if (!customPadding) {
recycler.updatePaddingRelative(
top = activity!!.toolbar.y.toInt() + appBarHeight
)
}
recycler.doOnApplyWindowInsets { view, insets, _ ->
val headerHeight = insets.systemWindowInsetTop + appBarHeight
if (!customPadding) view.updatePaddingRelative(
@ -295,3 +303,13 @@ fun Controller.withFadeTransaction(): RouterTransaction {
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler())
}
fun Controller.openInBrowser(url: String) {
try {
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
startActivity(intent)
} catch (e: Throwable) {
activity?.toast(e.message)
}
}

View File

@ -1,122 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:fitsSystemWindows="true"
android:layout_height="match_parent">
<ImageView
android:id="@+id/extension_icon"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/extension_title"
app:layout_constraintBottom_toBottomOf="@id/extension_pkg"
android:src="@mipmap/ic_launcher"/>
<TextView
android:id="@+id/extension_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
style="@style/TextAppearance.Regular.SubHeading"
app:layout_constraintStart_toEndOf="@id/extension_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="Tachiyomi: Extension"/>
<TextView
android:id="@+id/extension_version"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/extension_title"
app:layout_constraintStart_toStartOf="@id/extension_title"
tools:text="Version: 1.0.0" />
<TextView
android:id="@+id/extension_lang"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/extension_version"
app:layout_constraintStart_toStartOf="@id/extension_title"
tools:text="Language: English" />
<TextView
android:id="@+id/extension_pkg"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:singleLine="true"
android:ellipsize="middle"
app:layout_constraintTop_toBottomOf="@id/extension_lang"
app:layout_constraintStart_toStartOf="@id/extension_title"
app:layout_constraintEnd_toEndOf="parent"
tools:text="eu.kanade.tachiyomi.extension.en.myext"/>
<TextView
android:id="@+id/extension_obsolete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:padding="16dp"
android:text="@string/obsolete_extension_message"
android:textColor="@android:color/white"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/extension_pkg" />
<Button
android:id="@+id/extension_uninstall_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:textColor="@android:color/white"
android:text="@string/uninstall"
style="@style/Theme.Widget.Button.Colored"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/extension_obsolete" />
android:layout_height="wrap_content" >
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/extension_prefs_recycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/extension_uninstall_button"/>
<eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/extension_prefs_empty_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/extension_uninstall_button"/>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/extension_warning_banner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorError"
android:gravity="center"
android:padding="16dp"
android:textColor="?attr/colorOnError"
android:visibility="gone"
tools:visibility="visible" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/colorBackground"
android:elevation="2dp"
android:padding="16dp">
<ImageView
android:id="@+id/extension_icon"
android:layout_width="56dp"
android:layout_height="56dp"
android:src="@mipmap/ic_launcher"
app:layout_constraintBottom_toBottomOf="@id/extension_pkg"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/extension_title"
style="@style/TextAppearance.Regular.SubHeading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:elevation="3dp"
app:layout_constraintStart_toEndOf="@id/extension_icon"
app:layout_constraintTop_toTopOf="parent"
tools:text="Tachiyomi: Extension" />
<TextView
android:id="@+id/extension_version"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:elevation="3dp"
android:gravity="center"
app:layout_constraintStart_toStartOf="@id/extension_title"
app:layout_constraintTop_toBottomOf="@id/extension_title"
tools:text="Version: 1.0.0" />
<TextView
android:id="@+id/extension_lang"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:elevation="3dp"
android:gravity="center"
app:layout_constraintStart_toStartOf="@id/extension_title"
app:layout_constraintTop_toBottomOf="@id/extension_version"
tools:text="Language: English" />
<TextView
android:id="@+id/extension_nsfw"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:elevation="3dp"
android:gravity="center"
android:text="@string/may_contain_nsfw"
android:textColor="?attr/colorError"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/extension_title"
app:layout_constraintTop_toBottomOf="@id/extension_lang"
tools:visibility="visible" />
<TextView
android:id="@+id/extension_pkg"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:elevation="3dp"
android:ellipsize="middle"
android:singleLine="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/extension_title"
app:layout_constraintTop_toBottomOf="@id/extension_nsfw"
tools:text="eu.kanade.tachiyomi.extension.en.myext" />
<Button
android:id="@+id/extension_uninstall_button"
style="@style/Theme.Widget.Button.Colored"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingStart="32dp"
android:textAllCaps="false"
android:paddingEnd="32dp"
android:text="@string/uninstall"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/extension_pkg" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -0,0 +1,15 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_history"
android:icon="@drawable/ic_history_24dp"
android:title="@string/whats_new"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_app_info"
android:title="@string/app_info"
app:showAsAction="never" />
</menu>

View File

@ -260,12 +260,15 @@
<string name="untrusted_extension">Untrusted extension</string>
<string name="untrusted_extension_message">This extension was signed with an untrusted certificate and wasn\'t activated.\n\nA malicious extension could read any login credentials stored in Tachiyomi or execute arbitrary code.\n\nBy trusting this certificate you accept these risks.</string>
<string name="obsolete_extension_message">This extension is no longer available.</string>
<string name="unofficial_extension_message">This extension is not from the official Tachiyomi extensions list.</string>
<string name="version_">Version: %1$s</string>
<string name="language_">Language: %1$s</string>
<string name="empty_preferences_for_extension">No preferences to edit for this extension</string>
<string name="nsfw_short">18+</string>
<string name="unofficial">Unofficial</string>
<string name="may_contain_nsfw">May contain NSFW (18+) content</string>
<string name="app_info">App info</string>
<string name="_must_be_enabled_first">%1$s must be enabled first</string>
<plurals name="extension_updates_available">
<item quantity="one">Extension update available</item>
<item quantity="other">%d extension updates available</item>
@ -636,7 +639,8 @@
<string name="build_time">Build time</string>
<string name="send_crash_report">Send crash reports</string>
<string name="helps_fix_bugs">Helps fix any bugs. No sensitive data will be sent</string>
<string name="whats_new">What\'s new in this release</string>
<string name="whats_new">What\'s new</string>
<string name="whats_new_this_release">What\'s new in this release</string>
<string name="website">Website</string>
<string name="open_source_licenses">Open source licenses</string>
@ -756,6 +760,7 @@
<string name="download_unread">Download unread</string>
<string name="drag_handle">Drag handle</string>
<string name="edit">Edit</string>
<string name="enable">Enable</string>
<string name="enabled">Enabled</string>
<string name="fast">Fast</string>
<string name="filter">Filter</string>