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.appcompat:appcompat:1.2.0")
implementation("androidx.cardview:cardview:1.0.0") implementation("androidx.cardview:cardview:1.0.0")
implementation("com.google.android.material:material:1.3.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.preference:preference:1.1.1")
implementation("androidx.annotation:annotation:1.1.0") implementation("androidx.annotation:annotation:1.1.0")
implementation("androidx.browser:browser:1.3.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) { class PreferencesHelper(val context: Context) {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context) private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
@ -135,7 +143,7 @@ class PreferencesHelper(val context: Context) {
fun browseAsList() = rxPrefs.getBoolean(Keys.catalogueAsList, false) 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) fun sourceSorting() = rxPrefs.getInteger(Keys.sourcesSort, 0)
@ -211,7 +219,7 @@ class PreferencesHelper(val context: Context) {
fun collapsedCategories() = rxPrefs.getStringSet("collapsed_categories", mutableSetOf()) 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()) fun pinnedCatalogues() = rxPrefs.getStringSet("pinned_catalogues", emptySet())

View File

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

View File

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

View File

@ -2,38 +2,30 @@ package eu.kanade.tachiyomi.ui.extension
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout 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.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R 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.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.extension.model.Extension 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.main.MainActivity
import eu.kanade.tachiyomi.ui.migration.MangaAdapter import eu.kanade.tachiyomi.ui.migration.MangaAdapter
import eu.kanade.tachiyomi.ui.migration.MangaItem import eu.kanade.tachiyomi.ui.migration.MangaItem
import eu.kanade.tachiyomi.ui.migration.SourceAdapter import eu.kanade.tachiyomi.ui.migration.SourceAdapter
import eu.kanade.tachiyomi.ui.migration.SourceItem import eu.kanade.tachiyomi.ui.migration.SourceItem
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController 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.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.collapse
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.expand import eu.kanade.tachiyomi.util.view.expand
import eu.kanade.tachiyomi.util.view.isExpanded 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.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.util.view.withFadeTransaction
import eu.kanade.tachiyomi.widget.ViewPagerAdapter 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.migration_controller.*
import kotlinx.android.synthetic.main.recents_controller.* import kotlinx.android.synthetic.main.recents_controller.*
import kotlinx.android.synthetic.main.recycler_with_scroller.view.* 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.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -244,9 +233,11 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
.showDialog(controller.router) .showDialog(controller.router)
} }
fun setExtensions(extensions: List<ExtensionItem>) { fun setExtensions(extensions: List<ExtensionItem>, updateController: Boolean = true) {
this.extensions = extensions this.extensions = extensions
controller.presenter.updateSources() if (updateController) {
controller.presenter.updateSources()
}
drawExtensions() drawExtensions()
} }

View File

@ -40,7 +40,7 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
// Set source name // Set source name
ext_title.text = extension.name ext_title.text = extension.name
version.text = extension.versionName version.text = extension.versionName
lang.text = LocaleHelper.getDisplayName(extension.lang, itemView.context) lang.text = LocaleHelper.getDisplayName(extension.lang)
warning.text = when { warning.text = when {
extension is Extension.Untrusted -> itemView.context.getString(R.string.untrusted) extension is Extension.Untrusted -> itemView.context.getString(R.string.untrusted)
extension is Extension.Installed && extension.isObsolete -> itemView.context.getString(R.string.obsolete) 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) { override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
titleRes = R.string.filter titleRes = R.string.filter
val activeLangs = preferences.enabledLanguages().getOrDefault() val activeLangs = preferences.enabledLanguages().get()
val availableLangs = val availableLangs =
Injekt.get<ExtensionManager>().availableExtensions.groupBy { Injekt.get<ExtensionManager>().availableExtensions.groupBy {
@ -31,13 +31,13 @@ class SettingsExtensionsController : SettingsController() {
availableLangs.forEach { availableLangs.forEach {
SwitchPreference(context).apply { SwitchPreference(context).apply {
preferenceScreen.addPreference(this) preferenceScreen.addPreference(this)
title = LocaleHelper.getDisplayName(it, context) title = LocaleHelper.getSourceDisplayName(it, context)
isPersistent = false isPersistent = false
isChecked = it in activeLangs isChecked = it in activeLangs
onChange { newValue -> onChange { newValue ->
val checked = newValue as Boolean val checked = newValue as Boolean
val currentActiveLangs = preferences.enabledLanguages().getOrDefault() val currentActiveLangs = preferences.enabledLanguages().get()
if (checked) { if (checked) {
preferences.enabledLanguages().set(currentActiveLangs + it) 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.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.util.TypedValue import android.util.TypedValue
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
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.preference.DialogPreference import androidx.preference.DialogPreference
@ -19,20 +25,33 @@ import androidx.preference.Preference
import androidx.preference.PreferenceGroupAdapter import androidx.preference.PreferenceGroupAdapter
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL import androidx.preference.SwitchPreferenceCompat
import com.jakewharton.rxbinding.view.clicks import androidx.recyclerview.widget.ConcatAdapter
import com.google.android.material.snackbar.Snackbar
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore 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.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.ConfigurableSource
import eu.kanade.tachiyomi.source.Source 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.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.system.LocaleHelper
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener import eu.kanade.tachiyomi.util.view.openInBrowser
import eu.kanade.tachiyomi.util.view.applyWindowInsetsForController import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.widget.preference.ListMatPreference import eu.kanade.tachiyomi.widget.preference.ListMatPreference
import kotlinx.android.synthetic.main.extension_detail_controller.* 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") @SuppressLint("RestrictedApi")
class ExtensionDetailsController(bundle: Bundle? = null) : class ExtensionDetailsController(bundle: Bundle? = null) :
@ -44,6 +63,13 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
private var preferenceScreen: PreferenceScreen? = null private var preferenceScreen: PreferenceScreen? = null
private val preferences: PreferencesHelper = Injekt.get()
private val viewScope = MainScope()
init {
setHasOptionsMenu(true)
}
constructor(pkgName: String) : this( constructor(pkgName: String) : this(
Bundle().apply { Bundle().apply {
putString(PKGNAME_KEY, pkgName) putString(PKGNAME_KEY, pkgName)
@ -67,24 +93,11 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
@SuppressLint("PrivateResource") @SuppressLint("PrivateResource")
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
view.applyWindowInsetsForController() scrollViewWith(extension_prefs_recycler, padBottom = true)
val extension = presenter.extension ?: return val extension = presenter.extension ?: return
val context = view.context 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 themedContext by lazy { getPreferenceThemeContext() }
val manager = PreferenceManager(themedContext) val manager = PreferenceManager(themedContext)
manager.preferenceDataStore = EmptyPreferenceDataStore() manager.preferenceDataStore = EmptyPreferenceDataStore()
@ -93,10 +106,12 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
preferenceScreen = screen preferenceScreen = screen
val multiSource = extension.sources.size > 1 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) { 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 = extension_prefs_recycler.layoutManager =
androidx.recyclerview.widget.LinearLayoutManager(context) androidx.recyclerview.widget.LinearLayoutManager(context)
extension_prefs_recycler.adapter = PreferenceGroupAdapter(screen) val concatAdapterConfig = ConcatAdapter.Config.Builder()
extension_prefs_recycler.addItemDecoration(androidx.recyclerview.widget.DividerItemDecoration(context, VERTICAL)) .setStableIdMode(ConcatAdapter.Config.StableIdMode.ISOLATED_STABLE_IDS)
extension_prefs_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) .build()
screen.setShouldUseGeneratedIds(true)
if (screen.preferenceCount == 0) { val extHeaderAdapter = ExtensionDetailsHeaderAdapter(presenter)
extension_prefs_empty_view.show( extHeaderAdapter.setHasStableIds(true)
R.drawable.ic_no_settings_24dp, extension_prefs_recycler.adapter = ConcatAdapter(concatAdapterConfig,
R.string.empty_preferences_for_extension extHeaderAdapter,
) PreferenceGroupAdapter(screen)
} )
extension_prefs_recycler.addItemDecoration(ExtensionSettingsDividerItemDecoration(context))
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
@ -135,41 +151,113 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
lastOpenPreferencePosition = savedInstanceState.get(LASTOPENPREFERENCE_KEY) as? Int 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 val context = screen.context
// TODO // TODO
val dataStore = SharedPreferencesDataStore(/*if (source is HttpSource) { val dataStore = SharedPreferencesDataStore(
source.preferences
} else {*/
context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE) context.getSharedPreferences("source_${source.id}", Context.MODE_PRIVATE)
/*}*/
) )
if (source is ConfigurableSource) { if (source is ConfigurableSource) {
if (multiSource) { val prefs = mutableListOf<Preference>()
screen.preferenceCategory { val block: (@DSL SwitchPreferenceCompat).() -> Unit = {
title = source.toString() 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) val newScreen = screen.preferenceManager.createPreferenceScreen(context)
screen.switchPreference(block)
source.setupPreferenceScreen(newScreen) source.setupPreferenceScreen(newScreen)
// Reparent the preferences // Reparent the preferences
while (newScreen.preferenceCount != 0) { while (newScreen.preferenceCount != 0) {
val pref = newScreen.getPreference(0) val pref = newScreen.getPreference(0)
pref.isIconSpaceReserved = false pref.isIconSpaceReserved = true
pref.preferenceDataStore = dataStore pref.preferenceDataStore = dataStore
pref.fragment = "source_${source.id}" 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) newScreen.removePreference(pref)
screen.addPreference(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 { private fun getPreferenceThemeContext(): Context {
val tv = TypedValue() val tv = TypedValue()
activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true) activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
@ -216,6 +304,15 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
f.showDialog(router) 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") @Suppress("UNCHECKED_CAST")
override fun <T : Preference> findPreference(key: CharSequence): T? { override fun <T : Preference> findPreference(key: CharSequence): T? {
// We track [lastOpenPreferencePosition] when displaying the dialog // We track [lastOpenPreferencePosition] when displaying the dialog
@ -226,5 +323,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
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" 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 android.os.Bundle
import eu.kanade.tachiyomi.extension.ExtensionManager 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) { fun bind(source: HttpSource, sourceEnabled: Boolean) {
val preferences by injectLazy<PreferencesHelper>() val preferences by injectLazy<PreferencesHelper>()
val isMultiLanguage = preferences.enabledLanguages().getOrDefault().size > 1 val isMultiLanguage = preferences.enabledLanguages().get().size > 1
// Set capitalized title. // Set capitalized title.
val sourceName = if (isMultiLanguage) source.toString() else source.name.capitalize() val sourceName = if (isMultiLanguage) source.toString() else source.name.capitalize()
title.text = sourceName title.text = sourceName

View File

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

View File

@ -85,7 +85,7 @@ class AboutController : SettingsController() {
} }
preferenceCategory { preferenceCategory {
preference { preference {
titleRes = R.string.whats_new titleRes = R.string.whats_new_this_release
onClick { onClick {
val intent = Intent( val intent = Intent(
Intent.ACTION_VIEW, 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 { inline fun PreferenceGroup.infoPreference(@StringRes infoRes: Int): Preference {
return initThenAdd( return initThenAdd(
Preference(context), Preference(context),

View File

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

View File

@ -12,6 +12,6 @@ class LangHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.Vie
BaseFlexibleViewHolder(view, adapter) { BaseFlexibleViewHolder(view, adapter) {
fun bind(item: LangItem) { 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) { override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type) super.onChangeStarted(handler, type)
if (!type.isPush && handler is SettingsSourcesFadeChangeHandler) { if (!type.isPush) {
ext_bottom_sheet.updateExtTitle() ext_bottom_sheet.updateExtTitle()
ext_bottom_sheet.presenter.refreshExtensions() ext_bottom_sheet.presenter.refreshExtensions()
presenter.updateSources()
} }
if (!type.isEnter) { if (!type.isEnter) {
ext_bottom_sheet.canExpand = false ext_bottom_sheet.canExpand = false
@ -358,7 +359,7 @@ class SourceController :
fun hideCatalogue(position: Int) { fun hideCatalogue(position: Int) {
val source = (adapter?.getItem(position) as? SourceItem)?.source ?: return 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()) preferences.hiddenSources().set(current + source.id.toString())
presenter.updateSources() presenter.updateSources()
@ -366,7 +367,7 @@ class SourceController :
snackbar = view?.snack(R.string.source_hidden, Snackbar.LENGTH_INDEFINITE) { snackbar = view?.snack(R.string.source_hidden, Snackbar.LENGTH_INDEFINITE) {
anchorView = ext_bottom_sheet anchorView = ext_bottom_sheet
setAction(R.string.undo) { setAction(R.string.undo) {
val newCurrent = preferences.hiddenSources().getOrDefault() val newCurrent = preferences.hiddenSources().get()
preferences.hiddenSources().set(newCurrent - source.id.toString()) preferences.hiddenSources().set(newCurrent - source.id.toString())
presenter.updateSources() presenter.updateSources()
} }

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.util.view
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -11,6 +12,7 @@ import android.view.inputmethod.InputMethodManager
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.math.MathUtils import androidx.core.math.MathUtils
import androidx.core.net.toUri
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.bluelinelabs.conductor.Controller 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.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -120,6 +123,11 @@ fun Controller.scrollViewWith(
val randomTag = Random.nextLong() val randomTag = Random.nextLong()
var lastY = 0f var lastY = 0f
var fakeToolbarView: View? = null var fakeToolbarView: View? = null
if (!customPadding) {
recycler.updatePaddingRelative(
top = activity!!.toolbar.y.toInt() + appBarHeight
)
}
recycler.doOnApplyWindowInsets { view, insets, _ -> recycler.doOnApplyWindowInsets { view, insets, _ ->
val headerHeight = insets.systemWindowInsetTop + appBarHeight val headerHeight = insets.systemWindowInsetTop + appBarHeight
if (!customPadding) view.updatePaddingRelative( if (!customPadding) view.updatePaddingRelative(
@ -295,3 +303,13 @@ fun Controller.withFadeTransaction(): RouterTransaction {
.pushChangeHandler(FadeChangeHandler()) .pushChangeHandler(FadeChangeHandler())
.popChangeHandler(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"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/coordinator"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:fitsSystemWindows="true" android:layout_height="wrap_content" >
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" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/extension_prefs_recycler" android:id="@+id/extension_prefs_recycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:clipToPadding="false" android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent" android:layout_width="match_parent"
app:layout_constraintStart_toStartOf="parent" android:layout_height="wrap_content" />
app:layout_constraintEnd_toEndOf="parent" </androidx.coordinatorlayout.widget.CoordinatorLayout>
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>

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">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="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="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="version_">Version: %1$s</string>
<string name="language_">Language: %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="empty_preferences_for_extension">No preferences to edit for this extension</string>
<string name="nsfw_short">18+</string> <string name="nsfw_short">18+</string>
<string name="unofficial">Unofficial</string> <string name="unofficial">Unofficial</string>
<string name="may_contain_nsfw">May contain NSFW (18+) content</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"> <plurals name="extension_updates_available">
<item quantity="one">Extension update available</item> <item quantity="one">Extension update available</item>
<item quantity="other">%d extension updates available</item> <item quantity="other">%d extension updates available</item>
@ -636,7 +639,8 @@
<string name="build_time">Build time</string> <string name="build_time">Build time</string>
<string name="send_crash_report">Send crash reports</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="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="website">Website</string>
<string name="open_source_licenses">Open source licenses</string> <string name="open_source_licenses">Open source licenses</string>
@ -756,6 +760,7 @@
<string name="download_unread">Download unread</string> <string name="download_unread">Download unread</string>
<string name="drag_handle">Drag handle</string> <string name="drag_handle">Drag handle</string>
<string name="edit">Edit</string> <string name="edit">Edit</string>
<string name="enable">Enable</string>
<string name="enabled">Enabled</string> <string name="enabled">Enabled</string>
<string name="fast">Fast</string> <string name="fast">Fast</string>
<string name="filter">Filter</string> <string name="filter">Filter</string>