diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 47203fe896..96b7e570cd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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") diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 92f684920a..f61ed0c33c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -43,6 +43,14 @@ private class DateFormatConverter : Preference.Adapter { } } +operator fun com.tfcporciuncula.flow.Preference>.plusAssign(item: T) { + set(get() + item) +} + +operator fun com.tfcporciuncula.flow.Preference>.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()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt b/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt index 2708b81e3a..6772622897 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/Source.kt @@ -97,3 +97,5 @@ interface Source : tachiyomi.source.Source { fun Source.icon(): Drawable? = Injekt.get().getAppIconForSource(this) + +fun Source.getPreferenceKey(): String = "source_$id" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt index e25257b711..05bea5874a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt @@ -146,7 +146,7 @@ class ExtensionBottomPresenter( @Synchronized private fun toItems(tuple: ExtensionTuple): List { 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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt index 35fa827230..1ec636ba4e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt @@ -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) { + fun setExtensions(extensions: List, updateController: Boolean = true) { this.extensions = extensions - controller.presenter.updateSources() + if (updateController) { + controller.presenter.updateSources() + } drawExtensions() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt index dcce041e44..2879eb4547 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/SettingsExtensionsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/SettingsExtensionsController.kt index bc3c565743..73f9140952 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/SettingsExtensionsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/SettingsExtensionsController.kt @@ -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().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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsController.kt similarity index 53% rename from app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsController.kt index 44dc8c4ef3..334241c578 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsController.kt @@ -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() + 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? = null): Boolean { + return (lang in langs ?: preferences.enabledLanguages().get()) + } + @Suppress("UNCHECKED_CAST") override fun 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" } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsHeaderAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsHeaderAdapter.kt new file mode 100644 index 0000000000..487c094a03 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsHeaderAdapter.kt @@ -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() { + + 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) + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsPresenter.kt similarity index 96% rename from app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPresenter.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsPresenter.kt index 51e58c34b7..c6f817ba66 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionDetailsPresenter.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionSettingsDividerItemDecoration.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionSettingsDividerItemDecoration.kt new file mode 100644 index 0000000000..37f399f8c5 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/details/ExtensionSettingsDividerItemDecoration.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt index 4f650de380..043dd98450 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/MigrationSourceHolder.kt @@ -18,7 +18,7 @@ class MigrationSourceHolder(view: View, val adapter: MigrationSourceAdapter) : fun bind(source: HttpSource, sourceEnabled: Boolean) { val preferences by injectLazy() - 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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt index 46c642ed08..ca93125655 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/design/PreMigrationController.kt @@ -142,7 +142,7 @@ class PreMigrationController(bundle: Bundle? = null) : * @return list containing enabled sources. */ private fun getEnabledSources(): List { - val languages = prefs.enabledLanguages().getOrDefault() + val languages = prefs.enabledLanguages().get() val sourcesSaved = prefs.migrationSources().get().split("/") var sources = sourceManager.getCatalogueSources() .filterIsInstance() @@ -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() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AboutController.kt index 6923078470..009400490f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AboutController.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt index a8439f877b..6078b631d2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt @@ -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), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBrowseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBrowseController.kt index 50375f898a..54fc512389 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBrowseController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBrowseController.kt @@ -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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt index b179ffe0f0..efd401419d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt @@ -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) { - 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?): List { 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 { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangHolder.kt index 6a1d5eaa5d..081be5b850 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangHolder.kt @@ -12,6 +12,6 @@ class LangHolder(view: View, adapter: FlexibleAdapter { - 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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/global_search/GlobalSearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/global_search/GlobalSearchPresenter.kt index 7ac2f4fe66..3c03cabda4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/global_search/GlobalSearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/global_search/GlobalSearchPresenter.kt @@ -101,8 +101,8 @@ open class GlobalSearchPresenter( * @return list containing enabled sources. */ protected open fun getEnabledSources(): List { - 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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/LocaleHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/LocaleHelper.kt index 44d8ac6501..37e7af64d2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/LocaleHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/LocaleHelper.kt @@ -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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt index d629540b04..61456c3c90 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ControllerExtensions.kt @@ -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) + } +} + diff --git a/app/src/main/res/layout/extension_detail_controller.xml b/app/src/main/res/layout/extension_detail_controller.xml index daa3a0c31e..b7c394b54f 100644 --- a/app/src/main/res/layout/extension_detail_controller.xml +++ b/app/src/main/res/layout/extension_detail_controller.xml @@ -1,122 +1,11 @@ - - - - - - - - - - - - - - -