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 d14a5c6f5e..3acee36aab 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 @@ -134,4 +134,6 @@ class PreferencesHelper(context: Context) { fun automaticUpdates() = prefs.getBoolean(keys.automaticUpdates, false) + fun hiddenCatalogues() = rxPrefs.getStringSet("hidden_catalogues", emptySet()) + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt index 42c073b025..b1f79b57c1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt @@ -16,6 +16,7 @@ import com.afollestad.materialdialogs.MaterialDialog import com.f2prateek.rx.preferences.Preference import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.source.online.LoginSource import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.main.MainActivity @@ -45,7 +46,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold /** * Spinner shown in the toolbar to change the selected source. */ - private lateinit var spinner: Spinner + private var spinner: Spinner? = null /** * Adapter containing the list of manga from the catalogue. @@ -125,6 +126,14 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold } override fun onViewCreated(view: View, savedState: Bundle?) { + // If the source list is empty or it only has unlogged sources, return to main screen. + val sources = presenter.sources + if (sources.isEmpty() || sources.all { it is LoginSource && !it.isLogged() }) { + context.toast(R.string.no_valid_sources) + activity.onBackPressed() + return + } + // Initialize adapter, scroll listener and recycler views adapter = CatalogueAdapter(this) @@ -166,7 +175,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold val onItemSelected = IgnoreFirstSpinnerListener { position -> val source = spinnerAdapter.getItem(position) if (!presenter.isValidSource(source)) { - spinner.setSelection(selectedIndex) + spinner?.setSelection(selectedIndex) context.toast(R.string.source_requires_login) } else if (source != presenter.source) { selectedIndex = position @@ -264,7 +273,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold searchItem?.let { if (it.isActionViewExpanded) it.collapseActionView() } - toolbar.removeView(spinner) + spinner?.let { toolbar.removeView(it) } super.onDestroyView() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt index ec81d486d9..37e3983aec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt @@ -21,6 +21,7 @@ import rx.schedulers.Schedulers import rx.subjects.PublishSubject import timber.log.Timber import uy.kohesive.injekt.injectLazy +import java.util.NoSuchElementException /** * Presenter of [CatalogueFragment]. @@ -103,7 +104,11 @@ class CataloguePresenter : BasePresenter() { override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) - source = getLastUsedSource() + try { + source = getLastUsedSource() + } catch (error: NoSuchElementException) { + return + } if (savedState != null) { query = savedState.getString(CataloguePresenter::query.name, "") @@ -324,6 +329,7 @@ class CataloguePresenter : BasePresenter() { */ private fun getEnabledSources(): List { val languages = prefs.enabledLanguages().getOrDefault() + val hiddenCatalogues = prefs.hiddenCatalogues().getOrDefault() // Ensure at least one language if (languages.isEmpty()) { @@ -332,6 +338,7 @@ class CataloguePresenter : BasePresenter() { return sourceManager.getOnlineSources() .filter { it.lang.code in languages } + .filterNot { it.id.toString() in hiddenCatalogues } .sortedBy { "(${it.lang.code}) ${it.name}" } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt index 56147e2566..e29c5369d1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt @@ -1,21 +1,19 @@ package eu.kanade.tachiyomi.ui.setting import android.content.Intent +import android.graphics.drawable.Drawable import android.os.Bundle -import android.support.v7.preference.Preference -import android.support.v7.preference.PreferenceGroup import android.support.v7.preference.XpPreferenceFragment import android.view.View import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.getLanguages -import eu.kanade.tachiyomi.data.source.online.LoginSource -import eu.kanade.tachiyomi.util.plusAssign -import eu.kanade.tachiyomi.widget.preference.LoginPreference +import eu.kanade.tachiyomi.widget.preference.LoginCheckBoxPreference import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog -import net.xpece.android.support.preference.MultiSelectListPreference +import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy class SettingsSourcesFragment : SettingsFragment() { @@ -32,59 +30,105 @@ class SettingsSourcesFragment : SettingsFragment() { private val preferences: PreferencesHelper by injectLazy() - private val sourceManager: SourceManager by injectLazy() + private val onlineSources by lazy { Injekt.get().getOnlineSources() } - val languagesPref by lazy { findPreference("pref_source_languages") as MultiSelectListPreference } - - val sourcesPref by lazy { findPreference("pref_sources") as PreferenceGroup } + override fun setDivider(divider: Drawable?) { + super.setDivider(null) + } override fun onViewCreated(view: View, savedState: Bundle?) { super.onViewCreated(view, savedState) + // Remove dummy preference + preferenceScreen.removeAll() + + // Get the list of active language codes. + val activeLangsCodes = preferences.enabledLanguages().getOrDefault() + + // Get the list of languages ordered by name. val langs = getLanguages().sortedBy { it.lang } - val entryKeys = langs.map { it.code } - languagesPref.entries = langs.map { it.lang }.toTypedArray() - languagesPref.entryValues = entryKeys.toTypedArray() - languagesPref.values = preferences.enabledLanguages().getOrDefault() + // Order first by active languages, then inactive ones + val orderedLangs = langs.filter { it.code in activeLangsCodes } + + langs.filterNot { it.code in activeLangsCodes } - subscriptions += preferences.enabledLanguages().asObservable() - .subscribe { languages -> - sourcesPref.removeAll() - - val enabledSources = sourceManager.getOnlineSources() - .filter { it.lang.code in languages } - - for (source in enabledSources.filterIsInstance(LoginSource::class.java)) { - val pref = createLoginSourceEntry(source) - sourcesPref.addPreference(pref) - } - - // Hide category if it doesn't have any child - sourcesPref.isVisible = sourcesPref.preferenceCount > 0 + orderedLangs.forEach { lang -> + // Create a preference group and set initial state and change listener + SwitchPreferenceCategory(context).apply { + preferenceScreen.addPreference(this) + title = lang.lang + isPersistent = false + if (lang.code in activeLangsCodes) { + setChecked(true) + addLanguageSources(this) } + + setOnPreferenceChangeListener { preference, any -> + val checked = any as Boolean + val current = preferences.enabledLanguages().getOrDefault() + if (!checked) { + preferences.enabledLanguages().set(current - lang.code) + removeAll() + } else { + preferences.enabledLanguages().set(current + lang.code) + addLanguageSources(this) + } + true + } + } + } } - fun createLoginSourceEntry(source: Source): Preference { - return LoginPreference(preferenceManager.context).apply { - key = preferences.keys.sourceUsername(source.id) - title = source.toString() + /** + * Adds the source list for the given group (language). + * + * @param group the language category. + */ + private fun addLanguageSources(group: SwitchPreferenceCategory) { + val sources = onlineSources.filter { it.lang.lang == group.title }.sortedBy { it.name } + val hiddenCatalogues = preferences.hiddenCatalogues().getOrDefault() + + sources.forEach { source -> + val sourcePreference = LoginCheckBoxPreference(context, source).apply { + val id = source.id.toString() + title = source.name + key = getSourceKey(source.id) + isPersistent = false + isChecked = id !in hiddenCatalogues + + setOnPreferenceChangeListener { preference, any -> + val checked = any as Boolean + val current = preferences.hiddenCatalogues().getOrDefault() + + preferences.hiddenCatalogues().set(if (checked) + current - id + else + current + id) + + true + } + + setOnLoginClickListener { + val fragment = SourceLoginDialog.newInstance(source) + fragment.setTargetFragment(this@SettingsSourcesFragment, SOURCE_CHANGE_REQUEST) + fragment.show(fragmentManager, null) + } - setOnPreferenceClickListener { - val fragment = SourceLoginDialog.newInstance(source) - fragment.setTargetFragment(this@SettingsSourcesFragment, SOURCE_CHANGE_REQUEST) - fragment.show(fragmentManager, null) - true } + group.addPreference(sourcePreference) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == SOURCE_CHANGE_REQUEST) { - val pref = findPreference(preferences.keys.sourceUsername(resultCode)) as? LoginPreference + val pref = findPreference(getSourceKey(resultCode)) as? LoginCheckBoxPreference pref?.notifyChanged() } } + private fun getSourceKey(sourceId: Int): String { + return "source_$sourceId" + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt new file mode 100644 index 0000000000..1d4ef5862c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt @@ -0,0 +1,56 @@ +package eu.kanade.tachiyomi.widget.preference + +import android.content.Context +import android.graphics.Color +import android.support.v7.preference.PreferenceViewHolder +import android.util.AttributeSet +import android.view.View +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.source.online.LoginSource +import eu.kanade.tachiyomi.data.source.online.OnlineSource +import eu.kanade.tachiyomi.util.setVectorCompat +import kotlinx.android.synthetic.main.pref_item_source.view.* +import net.xpece.android.support.preference.CheckBoxPreference + +class LoginCheckBoxPreference @JvmOverloads constructor( + context: Context, + val source: OnlineSource, + attrs: AttributeSet? = null +) : CheckBoxPreference(context, attrs) { + + init { + layoutResource = R.layout.pref_item_source + } + + private var onLoginClick: () -> Unit = {} + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + val loginFrame = holder.itemView.login_frame + if (source is LoginSource) { + val tint = if (source.isLogged()) + Color.argb(255, 76, 175, 80) + else + Color.argb(97, 0, 0, 0) + + holder.itemView.login.setVectorCompat(R.drawable.ic_account_circle_black_24dp, tint) + + loginFrame.visibility = View.VISIBLE + loginFrame.setOnClickListener { + onLoginClick() + } + } else { + loginFrame.visibility = View.GONE + } + } + + fun setOnLoginClickListener(block: () -> Unit) { + onLoginClick = block + } + + // Make method public + override public fun notifyChanged() { + super.notifyChanged() + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt new file mode 100644 index 0000000000..bf54136f16 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt @@ -0,0 +1,137 @@ +package eu.kanade.tachiyomi.widget.preference + +import android.annotation.TargetApi +import android.content.Context +import android.content.res.TypedArray +import android.os.Build +import android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH +import android.support.v7.preference.PreferenceViewHolder +import android.support.v7.widget.SwitchCompat +import android.util.AttributeSet +import android.view.View +import android.widget.Checkable +import android.widget.CompoundButton +import android.widget.Switch +import eu.kanade.tachiyomi.util.getResourceColor +import net.xpece.android.support.preference.PreferenceCategory +import net.xpece.android.support.preference.R + +class SwitchPreferenceCategory @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null) +: PreferenceCategory( + context, + attrs, + R.attr.switchPreferenceCompatStyle, + R.style.Preference_Material_SwitchPreferenceCompat), +CompoundButton.OnCheckedChangeListener { + + init { + setTitleTextColor(context.theme.getResourceColor(R.attr.colorAccent)) + } + + private var mChecked = false + + private var mCheckedSet = false + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + syncSwitchView(holder) + } + + private fun syncSwitchView(holder: PreferenceViewHolder) { + val switchView = holder.findViewById(R.id.switchWidget) + syncSwitchView(switchView) + } + + @TargetApi(ICE_CREAM_SANDWICH) + private fun syncSwitchView(view: View) { + if (view is Checkable) { + val isChecked = view.isChecked + if (isChecked == mChecked) return + + if (view is SwitchCompat) { + view.setOnCheckedChangeListener(null) + } else if (NATIVE_SWITCH_CAPABLE && view is Switch) { + view.setOnCheckedChangeListener(null) + } + + view.toggle() + + if (view is SwitchCompat) { + view.setOnCheckedChangeListener(this) + } else if (NATIVE_SWITCH_CAPABLE && view is Switch) { + view.setOnCheckedChangeListener(this) + } + } + } + + override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { + if (!callChangeListener(isChecked)) { + buttonView.isChecked = !isChecked + } else { + setChecked(isChecked) + } + } + + override fun onClick() { + super.onClick() + + val newValue = !isChecked() + if (callChangeListener(newValue)) { + setChecked(newValue) + } + } + + /** + * Sets the checked state and saves it to the [SharedPreferences]. + * + * @param checked The checked state. + */ + fun setChecked(checked: Boolean) { + // Always persist/notify the first time; don't assume the field's default of false. + val changed = mChecked != checked + if (changed || !mCheckedSet) { + mChecked = checked + mCheckedSet = true + persistBoolean(checked) + if (changed) { + notifyDependencyChange(shouldDisableDependents()) + notifyChanged() + } + } + } + + /** + * Returns the checked state. + * + * @return The checked state. + */ + fun isChecked(): Boolean { + return mChecked + } + + override fun isEnabled(): Boolean { + return true + } + + override fun shouldDisableDependents(): Boolean { + return false + } + + override fun onGetDefaultValue(a: TypedArray, index: Int): Any { + return a.getBoolean(index, false) + } + + override fun onSetInitialValue(restoreValue: Boolean, defaultValue: Any?) { + setChecked(if (restoreValue) + getPersistedBoolean(mChecked) + else + defaultValue as Boolean) + } + + companion object { + private val NATIVE_SWITCH_CAPABLE = Build.VERSION.SDK_INT >= ICE_CREAM_SANDWICH + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_account_circle_black_24dp.xml b/app/src/main/res/drawable/ic_account_circle_black_24dp.xml new file mode 100644 index 0000000000..76785806d6 --- /dev/null +++ b/app/src/main/res/drawable/ic_account_circle_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/pref_item_source.xml b/app/src/main/res/layout/pref_item_source.xml new file mode 100644 index 0000000000..27ff9b02ea --- /dev/null +++ b/app/src/main/res/layout/pref_item_source.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b5e7c1e300..ab4563c2fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -153,11 +153,6 @@ Fourth to last chapter Fifth to last chapter - - Languages - Select the languages to show sources from - Accounts - Services @@ -205,6 +200,7 @@ This source requires you to log in Select a source + Please enable at least one valid source Info diff --git a/app/src/main/res/xml/pref_sources.xml b/app/src/main/res/xml/pref_sources.xml index 31b2d99b77..aa0c38217b 100644 --- a/app/src/main/res/xml/pref_sources.xml +++ b/app/src/main/res/xml/pref_sources.xml @@ -6,15 +6,8 @@ android:persistent="false" android:title="@string/pref_category_sources"> - - - + +