Browse Source

Hide catalogues (#466)

Hide catalogues
inorichi 8 years ago
parent
commit
58a2f7a874

+ 2 - 0
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())
+
 }

+ 12 - 3
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<CataloguePresenter>(), 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<CataloguePresenter>(), 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<CataloguePresenter>(), 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<CataloguePresenter>(), FlexibleViewHold
         searchItem?.let {
             if (it.isActionViewExpanded) it.collapseActionView()
         }
-        toolbar.removeView(spinner)
+        spinner?.let { toolbar.removeView(it) }
         super.onDestroyView()
     }
 

+ 8 - 1
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<CatalogueFragment>() {
     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<CatalogueFragment>() {
      */
     private fun getEnabledSources(): List<OnlineSource> {
         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<CatalogueFragment>() {
 
         return sourceManager.getOnlineSources()
                 .filter { it.lang.code in languages }
+                .filterNot { it.id.toString() in hiddenCatalogues }
                 .sortedBy { "(${it.lang.code}) ${it.name}" }
     }
 

+ 81 - 37
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<SourceManager>().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)
 
-        val langs = getLanguages().sortedBy { it.lang }
+        // Remove dummy preference
+        preferenceScreen.removeAll()
 
-        val entryKeys = langs.map { it.code }
-        languagesPref.entries = langs.map { it.lang }.toTypedArray()
-        languagesPref.entryValues = entryKeys.toTypedArray()
-        languagesPref.values = preferences.enabledLanguages().getOrDefault()
+        // Get the list of active language codes.
+        val activeLangsCodes = preferences.enabledLanguages().getOrDefault()
 
-        subscriptions += preferences.enabledLanguages().asObservable()
-                .subscribe { languages ->
-                    sourcesPref.removeAll()
+        // Get the list of languages ordered by name.
+        val langs = getLanguages().sortedBy { it.lang }
 
-                    val enabledSources = sourceManager.getOnlineSources()
-                            .filter { it.lang.code in languages }
+        // Order first by active languages, then inactive ones
+        val orderedLangs = langs.filter { it.code in activeLangsCodes } +
+                langs.filterNot { it.code in activeLangsCodes }
+
+        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)
+                }
 
-                    for (source in enabledSources.filterIsInstance(LoginSource::class.java)) {
-                        val pref = createLoginSourceEntry(source)
-                        sourcesPref.addPreference(pref)
+                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)
                     }
-
-                    // Hide category if it doesn't have any child
-                    sourcesPref.isVisible = sourcesPref.preferenceCount > 0
+                    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"
+    }
+
 }

+ 56 - 0
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()
+    }
+
+}

+ 137 - 0
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
+    }
+
+}

+ 9 - 0
app/src/main/res/drawable/ic_account_circle_black_24dp.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,5c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zM12,19.2c-2.5,0 -4.71,-1.28 -6,-3.22 0.03,-1.99 4,-3.08 6,-3.08 1.99,0 5.97,1.09 6,3.08 -1.29,1.94 -3.5,3.22 -6,3.22z"/>
+</vector>

+ 62 - 0
app/src/main/res/layout/pref_item_source.xml

@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?selectableItemBackground"
+    android:baselineAligned="false"
+    android:clipToPadding="false"
+    android:gravity="center_vertical"
+    android:minHeight="42dp"
+    android:paddingLeft="?listPreferredItemPaddingLeft"
+    android:paddingRight="?listPreferredItemPaddingRight"
+    tools:ignore="RtlHardcoded">
+
+    <LinearLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:clipToPadding="false"
+        android:gravity="start|center_vertical"
+        android:orientation="vertical"
+        android:paddingLeft="16dp"
+        android:paddingRight="16dp"/>
+
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:ellipsize="marquee"
+        android:singleLine="true"
+        android:textAppearance="?textAppearanceListItem"/>
+
+    <!-- Hidden view -->
+    <TextView
+        android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone" />
+
+    <LinearLayout
+        android:id="@+id/login_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="-16dp"
+        android:layout_marginRight="-16dp"
+        android:clipToPadding="false"
+        android:gravity="end|center_vertical"
+        android:orientation="vertical"
+        android:paddingLeft="16dp"
+        android:paddingRight="16dp"
+        android:visibility="gone">
+
+        <ImageView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/login" />
+
+    </LinearLayout>
+
+
+</LinearLayout>

+ 1 - 5
app/src/main/res/values/strings.xml

@@ -153,11 +153,6 @@
     <string name="fourth_to_last">Fourth to last chapter</string>
     <string name="fifth_to_last">Fifth to last chapter</string>
 
-      <!-- Sources section -->
-    <string name="languages">Languages</string>
-    <string name="languages_summary">Select the languages to show sources from</string>
-    <string name="accounts">Accounts</string>
-
       <!-- Sync section -->
     <string name="services">Services</string>
 
@@ -205,6 +200,7 @@
     <!-- Catalogue fragment -->
     <string name="source_requires_login">This source requires you to log in</string>
     <string name="select_source">Select a source</string>
+    <string name="no_valid_sources">Please enable at least one valid source</string>
 
     <!-- Manga info fragment -->
     <string name="manga_detail_tab">Info</string>

+ 2 - 9
app/src/main/res/xml/pref_sources.xml

@@ -6,15 +6,8 @@
         android:persistent="false"
         android:title="@string/pref_category_sources">
 
-        <MultiSelectListPreference
-            android:key="@string/pref_source_languages"
-            android:title="@string/languages"
-            android:summary="@string/languages_summary"/>
-
-        <PreferenceCategory
-            android:key="pref_sources"
-            android:persistent="false"
-            android:title="@string/accounts"/>
+        <!-- Dummy preference, it's needed at least one  -->
+        <Preference/>
 
     </PreferenceScreen>