Browse Source

Merge pull request #209 from inorichi/source-languages

Support for sources from different languages
inorichi 10 năm trước cách đây
mục cha
commit
dc742f4b8d
36 tập tin đã thay đổi với 407 bổ sung và 173 xóa
  1. 2 2
      app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.java
  2. 5 3
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
  3. 8 0
      app/src/main/java/eu/kanade/tachiyomi/data/source/Language.kt
  4. 2 2
      app/src/main/java/eu/kanade/tachiyomi/data/source/SourceManager.kt
  5. 9 1
      app/src/main/java/eu/kanade/tachiyomi/data/source/base/BaseSource.java
  6. 7 1
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.java
  7. 7 1
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.java
  8. 7 1
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.java
  9. 7 1
      app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.java
  10. 0 3
      app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java
  11. 23 0
      app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt
  12. 6 2
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt
  13. 21 6
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt
  14. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.java
  15. 0 76
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAccountsFragment.kt
  16. 12 8
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt
  17. 2 2
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsNestedFragment.kt
  18. 88 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt
  19. 52 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSyncFragment.kt
  20. 2 2
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/LibraryColumnsDialog.kt
  21. 25 9
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt
  22. 34 0
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginPreference.kt
  23. 4 8
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/MangaSyncLoginDialog.kt
  24. 4 8
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt
  25. BIN
      app/src/main/res/drawable-xhdpi/bkg_shadow_img.9.png
  26. 9 0
      app/src/main/res/drawable/ic_done_green_24dp.xml
  27. 1 1
      app/src/main/res/layout/item_chapter.xml
  28. 2 2
      app/src/main/res/layout/pref_account_login.xml
  29. 7 0
      app/src/main/res/layout/preference_widget_imageview.xml
  30. 4 1
      app/src/main/res/values/keys.xml
  31. 11 3
      app/src/main/res/values/strings.xml
  32. 0 16
      app/src/main/res/xml/pref_accounts.xml
  33. 0 11
      app/src/main/res/xml/pref_general.xml
  34. 7 2
      app/src/main/res/xml/pref_main.xml
  35. 16 0
      app/src/main/res/xml/pref_sources.xml
  36. 22 0
      app/src/main/res/xml/pref_sync.xml

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.java

@@ -379,11 +379,11 @@ public class DownloadManager {
     }
 
     public File getAbsoluteMangaDirectory(Source source, Manga manga) {
-        String chapterRelativePath = source.getName() +
+        String mangaRelativePath = source.getVisibleName() +
                 File.separator +
                 manga.title.replaceAll("[^\\sa-zA-Z0-9.-]", "_");
 
-        return new File(preferences.getDownloadsDirectory(), chapterRelativePath);
+        return new File(preferences.getDownloadsDirectory(), mangaRelativePath);
     }
 
     // Get the absolute path to the chapter directory

+ 5 - 3
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -11,9 +11,7 @@ import eu.kanade.tachiyomi.data.source.base.Source
 import java.io.File
 import java.io.IOException
 
-fun <T> Preference<T>.getOrDefault(): T {
-    return get() ?: defaultValue()!!
-}
+fun <T> Preference<T>.getOrDefault(): T = get() ?: defaultValue()!!
 
 class PreferencesHelper(private val context: Context) {
 
@@ -140,6 +138,10 @@ class PreferencesHelper(private val context: Context) {
         return rxPrefs.getBoolean(getKey(R.string.pref_display_catalogue_as_list), false)
     }
 
+    fun enabledLanguages(): Preference<MutableSet<String>> {
+        return rxPrefs.getStringSet(getKey(R.string.pref_source_languages), setOf("EN"))
+    }
+
     fun getSourceUsername(source: Source): String {
         return prefs.getString(SOURCE_ACCOUNT_USERNAME + source.id, "")
     }

+ 8 - 0
app/src/main/java/eu/kanade/tachiyomi/data/source/Language.kt

@@ -0,0 +1,8 @@
+package eu.kanade.tachiyomi.data.source
+
+class Language(val lang: String, val code: String)
+
+val EN = Language("English", "EN")
+val RU = Language("Russian", "RU")
+
+fun getLanguages(): List<Language> = listOf(EN, RU)

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/source/SourceManager.kt

@@ -11,7 +11,6 @@ import java.util.*
 open class SourceManager(private val context: Context) {
 
     val sourcesMap: HashMap<Int, Source>
-    val sources: List<Source>
 
     val BATOTO = 1
     val MANGAHERE = 2
@@ -22,7 +21,6 @@ open class SourceManager(private val context: Context) {
 
     init {
         sourcesMap = createSourcesMap()
-        sources = ArrayList(sourcesMap.values).sortedBy { it.name }
     }
 
     open fun get(sourceKey: Int): Source? {
@@ -49,4 +47,6 @@ open class SourceManager(private val context: Context) {
         return map
     }
 
+    fun getSources(): List<Source> = ArrayList(sourcesMap.values)
+
 }

+ 9 - 1
app/src/main/java/eu/kanade/tachiyomi/data/source/base/BaseSource.java

@@ -6,6 +6,7 @@ import java.util.List;
 
 import eu.kanade.tachiyomi.data.database.models.Chapter;
 import eu.kanade.tachiyomi.data.database.models.Manga;
+import eu.kanade.tachiyomi.data.source.Language;
 import eu.kanade.tachiyomi.data.source.model.MangasPage;
 import okhttp3.Headers;
 import okhttp3.Response;
@@ -24,9 +25,16 @@ public abstract class BaseSource {
         this.id = id;
     }
 
+    public abstract Language getLang();
+
     // Name of the source to display
     public abstract String getName();
 
+    // Name of the source to display with the language
+    public String getVisibleName() {
+        return getName() + " (" + getLang().getCode() + ")";
+    }
+
     // Base url of the source, like: http://example.com
     public abstract String getBaseUrl();
 
@@ -86,6 +94,6 @@ public abstract class BaseSource {
 
     @Override
     public String toString() {
-        return getName();
+        return getVisibleName();
     }
 }

+ 7 - 1
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.java

@@ -28,6 +28,8 @@ import java.util.regex.Pattern;
 import eu.kanade.tachiyomi.data.database.models.Chapter;
 import eu.kanade.tachiyomi.data.database.models.Manga;
 import eu.kanade.tachiyomi.data.network.ReqKt;
+import eu.kanade.tachiyomi.data.source.Language;
+import eu.kanade.tachiyomi.data.source.LanguageKt;
 import eu.kanade.tachiyomi.data.source.base.LoginSource;
 import eu.kanade.tachiyomi.data.source.model.MangasPage;
 import eu.kanade.tachiyomi.data.source.model.Page;
@@ -40,7 +42,7 @@ import rx.Observable;
 
 public class Batoto extends LoginSource {
 
-    public static final String NAME = "Batoto (EN)";
+    public static final String NAME = "Batoto";
     public static final String BASE_URL = "http://bato.to";
     public static final String POPULAR_MANGAS_URL = BASE_URL + "/search_ajax?order_cond=views&order=desc&p=%s";
     public static final String SEARCH_URL = BASE_URL + "/search_ajax?name=%s&p=%s";
@@ -79,6 +81,10 @@ public class Batoto extends LoginSource {
         return BASE_URL;
     }
 
+    public Language getLang() {
+        return LanguageKt.getEN();
+    }
+
     @Override
     protected Headers.Builder headersBuilder() {
         Headers.Builder builder = super.headersBuilder();

+ 7 - 1
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.java

@@ -18,6 +18,8 @@ import java.util.regex.Pattern;
 import eu.kanade.tachiyomi.data.database.models.Chapter;
 import eu.kanade.tachiyomi.data.database.models.Manga;
 import eu.kanade.tachiyomi.data.network.ReqKt;
+import eu.kanade.tachiyomi.data.source.Language;
+import eu.kanade.tachiyomi.data.source.LanguageKt;
 import eu.kanade.tachiyomi.data.source.base.Source;
 import eu.kanade.tachiyomi.data.source.model.MangasPage;
 import eu.kanade.tachiyomi.data.source.model.Page;
@@ -28,7 +30,7 @@ import okhttp3.Request;
 
 public class Kissmanga extends Source {
 
-    public static final String NAME = "Kissmanga (EN)";
+    public static final String NAME = "Kissmanga";
     public static final String HOST = "kissmanga.com";
     public static final String IP = "93.174.95.110";
     public static final String BASE_URL = "http://" + IP;
@@ -56,6 +58,10 @@ public class Kissmanga extends Source {
         return BASE_URL;
     }
 
+    public Language getLang() {
+        return LanguageKt.getEN();
+    }
+
     @Override
     protected String getInitialPopularMangasUrl() {
         return String.format(POPULAR_MANGAS_URL, 1);

+ 7 - 1
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.java

@@ -18,13 +18,15 @@ import java.util.Locale;
 
 import eu.kanade.tachiyomi.data.database.models.Chapter;
 import eu.kanade.tachiyomi.data.database.models.Manga;
+import eu.kanade.tachiyomi.data.source.Language;
+import eu.kanade.tachiyomi.data.source.LanguageKt;
 import eu.kanade.tachiyomi.data.source.base.Source;
 import eu.kanade.tachiyomi.data.source.model.MangasPage;
 import eu.kanade.tachiyomi.util.Parser;
 
 public class Mangafox extends Source {
 
-    public static final String NAME = "Mangafox (EN)";
+    public static final String NAME = "Mangafox";
     public static final String BASE_URL = "http://mangafox.me";
     public static final String POPULAR_MANGAS_URL = BASE_URL + "/directory/%s";
     public static final String SEARCH_URL =
@@ -44,6 +46,10 @@ public class Mangafox extends Source {
         return BASE_URL;
     }
 
+    public Language getLang() {
+        return LanguageKt.getEN();
+    }
+
     @Override
     protected String getInitialPopularMangasUrl() {
         return String.format(POPULAR_MANGAS_URL, "");

+ 7 - 1
app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.java

@@ -18,13 +18,15 @@ import java.util.Locale;
 
 import eu.kanade.tachiyomi.data.database.models.Chapter;
 import eu.kanade.tachiyomi.data.database.models.Manga;
+import eu.kanade.tachiyomi.data.source.Language;
+import eu.kanade.tachiyomi.data.source.LanguageKt;
 import eu.kanade.tachiyomi.data.source.base.Source;
 import eu.kanade.tachiyomi.data.source.model.MangasPage;
 import eu.kanade.tachiyomi.util.Parser;
 
 public class Mangahere extends Source {
 
-    public static final String NAME = "Mangahere (EN)";
+    public static final String NAME = "Mangahere";
     public static final String BASE_URL = "http://www.mangahere.co";
     public static final String POPULAR_MANGAS_URL = BASE_URL + "/directory/%s";
     public static final String SEARCH_URL = BASE_URL + "/search.php?name=%s&page=%s&sort=views&order=za";
@@ -43,6 +45,10 @@ public class Mangahere extends Source {
         return BASE_URL;
     }
 
+    public Language getLang() {
+        return LanguageKt.getEN();
+    }
+
     @Override
     protected String getInitialPopularMangasUrl() {
         return String.format(POPULAR_MANGAS_URL, "");

+ 0 - 3
app/src/main/java/eu/kanade/tachiyomi/injection/component/AppComponent.java

@@ -24,7 +24,6 @@ import eu.kanade.tachiyomi.ui.manga.info.MangaInfoPresenter;
 import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListPresenter;
 import eu.kanade.tachiyomi.ui.reader.ReaderPresenter;
 import eu.kanade.tachiyomi.ui.recent.RecentChaptersPresenter;
-import eu.kanade.tachiyomi.ui.setting.SettingsAccountsFragment;
 import eu.kanade.tachiyomi.ui.setting.SettingsActivity;
 
 @Singleton
@@ -48,8 +47,6 @@ public interface AppComponent {
     void inject(RecentChaptersPresenter recentChaptersPresenter);
 
     void inject(MangaActivity mangaActivity);
-    void inject(SettingsAccountsFragment settingsAccountsFragment);
-
     void inject(SettingsActivity settingsActivity);
 
     void inject(Source source);

+ 23 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt

@@ -1,10 +1,15 @@
 package eu.kanade.tachiyomi.ui.base.activity
 
+import android.graphics.Color
 import android.os.Bundle
+import android.support.design.widget.Snackbar
 import android.support.v7.app.AppCompatActivity
 import android.support.v7.widget.Toolbar
 import android.view.MenuItem
+import android.view.View
+import android.widget.TextView
 import eu.kanade.tachiyomi.App
+import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.injection.component.AppComponent
 import icepick.Icepick
 import org.greenrobot.eventbus.EventBus
@@ -60,6 +65,24 @@ open class BaseActivity : AppCompatActivity() {
         EventBus.getDefault().unregister(this)
     }
 
+    fun snack(text: String?, duration: Int = Snackbar.LENGTH_LONG) {
+        val snack = Snackbar.make(findViewById(android.R.id.content), text ?: getString(R.string.unknown_error), duration)
+        val textView = snack.view.findViewById(android.support.design.R.id.snackbar_text) as TextView
+        textView.setTextColor(Color.WHITE)
+        snack.show()
+    }
+
+    fun snack(text: String?, actionRes: Int, actionFunc: () -> Unit,
+              duration: Int = Snackbar.LENGTH_LONG, view: View = findViewById(android.R.id.content)) {
+
+        val snack = Snackbar.make(view, text ?: getString(R.string.unknown_error), duration)
+                .setAction(actionRes, { actionFunc() })
+
+        val textView = snack.view.findViewById(android.support.design.R.id.snackbar_text) as TextView
+        textView.setTextColor(Color.WHITE)
+        snack.show()
+    }
+
     protected val applicationComponent: AppComponent
         get() = App.get(this).component
 

+ 6 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt

@@ -168,7 +168,7 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
         val themedContext = baseActivity.supportActionBar?.themedContext ?: activity
 
         val spinnerAdapter = ArrayAdapter(themedContext,
-                android.R.layout.simple_spinner_item, presenter.getEnabledSources())
+                android.R.layout.simple_spinner_item, presenter.sources)
         spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
 
         val onItemSelected = object : AdapterView.OnItemSelectedListener {
@@ -353,8 +353,12 @@ class CatalogueFragment : BaseRxFragment<CataloguePresenter>(), FlexibleViewHold
      */
     fun onAddPageError(error: Throwable) {
         hideProgressBar()
-        ToastUtil.showShort(context, error.message)
         Timber.e(error, error.message)
+
+        baseActivity.snack(error.message, R.string.action_retry, {
+            showProgressBar()
+            presenter.retryRequest()
+        })
     }
 
     /**

+ 21 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt

@@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.cache.CoverCache
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
 import eu.kanade.tachiyomi.data.source.SourceManager
 import eu.kanade.tachiyomi.data.source.base.Source
 import eu.kanade.tachiyomi.data.source.model.MangasPage
@@ -45,7 +46,7 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
     /**
      * Enabled sources.
      */
-    private val sources by lazy { sourceManager.sources }
+    val sources by lazy { getEnabledSources() }
 
     /**
      * Active source.
@@ -188,6 +189,13 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
         }
     }
 
+    /**
+     * Retry a failed request.
+     */
+    fun retryRequest() {
+        start(GET_MANGA_PAGE)
+    }
+
     /**
      * Returns the observable of the network request for a page.
      *
@@ -308,12 +316,19 @@ class CataloguePresenter : BasePresenter<CatalogueFragment>() {
     }
 
     /**
-     * Returns a list of enabled sources.
-     *
-     * TODO filter by enabled sources.
+     * Returns a list of enabled sources ordered by language and name.
      */
-    fun getEnabledSources(): List<Source> {
-        return sourceManager.sources
+    private fun getEnabledSources(): List<Source> {
+        val languages = prefs.enabledLanguages().getOrDefault()
+
+        // Ensure at least one language
+        if (languages.isEmpty()) {
+            languages.add("EN")
+        }
+
+        return sourceManager.getSources()
+                .filter { it.lang.code in languages }
+                .sortedBy { "(${it.lang.code}) ${it.name}" }
     }
 
     /**

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.java

@@ -143,7 +143,7 @@ public class MangaInfoFragment extends BaseRxFragment<MangaInfoPresenter> {
 
         // If manga source is known update source TextView.
         if (mangaSource != null) {
-            source.setText(mangaSource.getName());
+            source.setText(mangaSource.getVisibleName());
         }
 
         // Update genres TextView.

+ 0 - 76
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAccountsFragment.kt

@@ -1,76 +0,0 @@
-package eu.kanade.tachiyomi.ui.setting
-
-import android.content.Context
-import android.os.Bundle
-import android.support.v7.preference.DialogPreference
-import android.support.v7.preference.Preference
-import android.support.v7.preference.PreferenceCategory
-import android.view.View
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.source.base.Source
-import eu.kanade.tachiyomi.widget.preference.MangaSyncLoginDialog
-import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog
-
-class SettingsAccountsFragment : SettingsNestedFragment() {
-
-    companion object {
-
-        fun newInstance(resourcePreference: Int, resourceTitle: Int): SettingsNestedFragment {
-            val fragment = SettingsAccountsFragment()
-            fragment.setArgs(resourcePreference, resourceTitle)
-            return fragment
-        }
-    }
-
-    val sourceCategory by lazy { findPreference("pref_category_source_accounts") as PreferenceCategory }
-    val syncCategory by lazy { findPreference("pref_category_manga_sync_accounts") as PreferenceCategory }
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        val themedContext = preferenceManager.context
-
-        for (source in getSourcesWithLogin()) {
-            val pref = SourcePreference(themedContext).apply {
-                isPersistent = false
-                title = source.name
-                key = source.id.toString()
-                dialogLayoutResource = R.layout.pref_account_login
-            }
-
-            sourceCategory.addPreference(pref)
-        }
-
-        for (sync in settingsActivity.syncManager.services) {
-            val pref = SyncPreference(themedContext).apply {
-                isPersistent = false
-                title = sync.name
-                key = sync.id.toString()
-                dialogLayoutResource = R.layout.pref_account_login
-            }
-
-            syncCategory.addPreference(pref)
-        }
-    }
-
-    fun getSourcesWithLogin(): List<Source> {
-        return settingsActivity.sourceManager.sources.filter { it.isLoginRequired }
-    }
-
-    override fun onDisplayPreferenceDialog(preference: Preference) {
-        if (preference is SourcePreference) {
-            val fragment = SourceLoginDialog.newInstance(preference)
-            fragment.setTargetFragment(this, 0)
-            fragment.show(childFragmentManager, null)
-        } else if (preference is SyncPreference) {
-            val fragment = MangaSyncLoginDialog.newInstance(preference)
-            fragment.setTargetFragment(this, 0)
-            fragment.show(childFragmentManager, null)
-        } else {
-            super.onDisplayPreferenceDialog(preference)
-        }
-    }
-
-    class SourcePreference(context: Context) : DialogPreference(context) {}
-
-    class SyncPreference(context: Context) : DialogPreference(context) {}
-
-}

+ 12 - 8
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt

@@ -1,7 +1,7 @@
 package eu.kanade.tachiyomi.ui.setting
 
 import android.os.Bundle
-import android.support.v7.preference.PreferenceFragmentCompat
+import android.support.v14.preference.PreferenceFragment
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.cache.ChapterCache
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
@@ -28,19 +28,19 @@ class SettingsActivity : BaseActivity() {
         setupToolbar(toolbar)
 
         if (savedState == null) {
-            supportFragmentManager.beginTransaction()
-                    .replace(R.id.settings_content,SettingsMainFragment())
+            fragmentManager.beginTransaction()
+                    .replace(R.id.settings_content, SettingsMainFragment())
                     .commit()
         }
     }
 
     override fun onBackPressed() {
-        if (!supportFragmentManager.popBackStackImmediate()) {
+        if (!fragmentManager.popBackStackImmediate()) {
             super.onBackPressed()
         }
     }
 
-    class SettingsMainFragment : PreferenceFragmentCompat() {
+    class SettingsMainFragment : PreferenceFragment() {
 
         override fun onCreatePreferences(savedState: Bundle?, s: String?) {
             addPreferencesFromResource(R.xml.pref_main)
@@ -57,8 +57,12 @@ class SettingsActivity : BaseActivity() {
                 SettingsDownloadsFragment.newInstance(R.xml.pref_downloads, R.string.pref_category_downloads)
             }
 
-            registerSubpreference(R.string.pref_category_accounts_key) {
-                SettingsAccountsFragment.newInstance(R.xml.pref_accounts, R.string.pref_category_accounts)
+            registerSubpreference(R.string.pref_category_sources_key) {
+                SettingsSourcesFragment.newInstance(R.xml.pref_sources, R.string.pref_category_sources)
+            }
+
+            registerSubpreference(R.string.pref_category_sync_key) {
+                SettingsSyncFragment.newInstance(R.xml.pref_sync, R.string.pref_category_sync)
             }
 
             registerSubpreference(R.string.pref_category_advanced_key) {
@@ -75,7 +79,7 @@ class SettingsActivity : BaseActivity() {
             (activity as BaseActivity).setToolbarTitle(getString(R.string.label_settings))
         }
 
-        private fun registerSubpreference(preferenceResource: Int, func: () -> PreferenceFragmentCompat) {
+        private fun registerSubpreference(preferenceResource: Int, func: () -> PreferenceFragment) {
             findPreference(getString(preferenceResource)).setOnPreferenceClickListener {
                 val fragment = func()
                 fragmentManager.beginTransaction()

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsNestedFragment.kt

@@ -1,10 +1,10 @@
 package eu.kanade.tachiyomi.ui.setting
 
 import android.os.Bundle
-import android.support.v7.preference.PreferenceFragmentCompat
+import android.support.v14.preference.PreferenceFragment
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 
-open class SettingsNestedFragment : PreferenceFragmentCompat() {
+open class SettingsNestedFragment : PreferenceFragment() {
 
     companion object {
 

+ 88 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt

@@ -0,0 +1,88 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.content.Intent
+import android.os.Bundle
+import android.support.v14.preference.MultiSelectListPreference
+import android.support.v7.preference.Preference
+import android.support.v7.preference.PreferenceGroup
+import android.view.View
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.data.source.base.Source
+import eu.kanade.tachiyomi.data.source.getLanguages
+import eu.kanade.tachiyomi.widget.preference.LoginPreference
+import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog
+import rx.Subscription
+
+class SettingsSourcesFragment : SettingsNestedFragment() {
+
+    companion object {
+        const val SOURCE_CHANGE_REQUEST = 120
+
+        fun newInstance(resourcePreference: Int, resourceTitle: Int): SettingsNestedFragment {
+            val fragment = SettingsSourcesFragment()
+            fragment.setArgs(resourcePreference, resourceTitle)
+            return fragment
+        }
+    }
+
+    val languagesPref by lazy { findPreference("pref_source_languages") as MultiSelectListPreference }
+    val sourcesPref by lazy { findPreference("pref_sources") as PreferenceGroup }
+
+    var languagesSubscription: Subscription? = null
+
+    override fun onViewCreated(view: View, savedState: Bundle?) {
+        val langs = getLanguages()
+
+        val entryKeys = langs.map { it.code }
+        languagesPref.entries = langs.map { it.lang }.toTypedArray()
+        languagesPref.entryValues = entryKeys.toTypedArray()
+        languagesPref.values = preferences.enabledLanguages().getOrDefault()
+
+        languagesSubscription = preferences.enabledLanguages().asObservable()
+                .subscribe { languages ->
+                    sourcesPref.removeAll()
+
+                    val enabledSources = settingsActivity.sourceManager.getSources()
+                            .filter { it.lang.code in languages }
+
+                    for (source in enabledSources) {
+                        if (source.isLoginRequired) {
+                            val pref = createSource(source)
+                            sourcesPref.addPreference(pref)
+                        }
+                    }
+
+                    // Hide category if it doesn't have any child
+                    sourcesPref.isVisible = sourcesPref.preferenceCount > 0
+                }
+    }
+
+    override fun onDestroyView() {
+        languagesSubscription?.unsubscribe()
+        super.onDestroyView()
+    }
+
+    fun createSource(source: Source): Preference {
+        return LoginPreference(preferenceManager.context).apply {
+            key = PreferencesHelper.SOURCE_ACCOUNT_USERNAME + source.id
+            title = source.visibleName
+
+            setOnPreferenceClickListener {
+                val fragment = SourceLoginDialog.newInstance(source)
+                fragment.setTargetFragment(this@SettingsSourcesFragment, SOURCE_CHANGE_REQUEST)
+                fragment.show(childFragmentManager, null)
+                true
+            }
+
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        if (requestCode == SOURCE_CHANGE_REQUEST) {
+            val pref = findPreference(PreferencesHelper.SOURCE_ACCOUNT_USERNAME + resultCode) as? LoginPreference
+            pref?.notifyChanged()
+        }
+    }
+
+}

+ 52 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSyncFragment.kt

@@ -0,0 +1,52 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.content.Intent
+import android.os.Bundle
+import android.support.v7.preference.PreferenceCategory
+import android.view.View
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.widget.preference.LoginPreference
+import eu.kanade.tachiyomi.widget.preference.MangaSyncLoginDialog
+
+class SettingsSyncFragment : SettingsNestedFragment() {
+
+    companion object {
+        const val SYNC_CHANGE_REQUEST = 121
+
+        fun newInstance(resourcePreference: Int, resourceTitle: Int): SettingsNestedFragment {
+            val fragment = SettingsSyncFragment()
+            fragment.setArgs(resourcePreference, resourceTitle)
+            return fragment
+        }
+    }
+
+    val syncCategory by lazy { findPreference("pref_category_manga_sync_accounts") as PreferenceCategory }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        val themedContext = preferenceManager.context
+
+        for (sync in settingsActivity.syncManager.services) {
+            val pref = LoginPreference(themedContext).apply {
+                key = PreferencesHelper.MANGASYNC_ACCOUNT_USERNAME + sync.id
+                title = sync.name
+
+                setOnPreferenceClickListener {
+                    val fragment = MangaSyncLoginDialog.newInstance(sync)
+                    fragment.setTargetFragment(this@SettingsSyncFragment, SYNC_CHANGE_REQUEST)
+                    fragment.show(childFragmentManager, null)
+                    true
+                }
+            }
+
+            syncCategory.addPreference(pref)
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        if (requestCode == SYNC_CHANGE_REQUEST) {
+            val pref = findPreference(PreferencesHelper.MANGASYNC_ACCOUNT_USERNAME + resultCode) as? LoginPreference
+            pref?.notifyChanged()
+        }
+    }
+
+}

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/widget/preference/LibraryColumnsDialog.kt

@@ -1,15 +1,15 @@
 package eu.kanade.tachiyomi.widget.preference
 
 import android.os.Bundle
+import android.support.v14.preference.PreferenceDialogFragment
 import android.support.v7.preference.Preference
-import android.support.v7.preference.PreferenceDialogFragmentCompat
 import android.view.View
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.data.preference.getOrDefault
 import eu.kanade.tachiyomi.ui.setting.SettingsActivity
 import kotlinx.android.synthetic.main.pref_library_columns.view.*
 
-class LibraryColumnsDialog : PreferenceDialogFragmentCompat() {
+class LibraryColumnsDialog : PreferenceDialogFragment() {
 
     companion object {
 

+ 25 - 9
app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt

@@ -1,18 +1,23 @@
 package eu.kanade.tachiyomi.widget.preference
 
-import android.support.v7.app.AlertDialog
-import android.support.v7.preference.PreferenceDialogFragmentCompat
+import android.app.Dialog
+import android.app.DialogFragment
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.Bundle
 import android.text.Editable
 import android.text.TextWatcher
 import android.text.method.PasswordTransformationMethod
 import android.view.View
+import com.afollestad.materialdialogs.MaterialDialog
 import com.dd.processbutton.iml.ActionProcessButton
+import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 import eu.kanade.tachiyomi.ui.setting.SettingsActivity
 import kotlinx.android.synthetic.main.pref_account_login.view.*
 import rx.Subscription
 
-abstract class LoginDialogPreference : PreferenceDialogFragmentCompat() {
+abstract class LoginDialogPreference : DialogFragment() {
 
     var v: View? = null
         private set
@@ -22,13 +27,18 @@ abstract class LoginDialogPreference : PreferenceDialogFragmentCompat() {
 
     var requestSubscription: Subscription? = null
 
-    override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
-        // Hide positive button
-        builder.setPositiveButton("", this)
+    override fun onCreateDialog(savedState: Bundle?): Dialog {
+        val dialog = MaterialDialog.Builder(activity)
+                .customView(R.layout.pref_account_login, false)
+                .negativeText(android.R.string.cancel)
+                .build();
+
+        onViewCreated(dialog.view, savedState)
+
+        return dialog
     }
 
-    override fun onBindDialogView(view: View) {
-        super.onBindDialogView(view)
+    override fun onViewCreated(view: View, savedState: Bundle?) {
         v = view.apply {
             show_password.setOnCheckedChangeListener { v, isChecked ->
                 if (isChecked)
@@ -59,10 +69,16 @@ abstract class LoginDialogPreference : PreferenceDialogFragmentCompat() {
 
     }
 
-    override fun onDialogClosed(positiveResult: Boolean) {
+    override fun onPause() {
+        super.onPause()
         requestSubscription?.unsubscribe()
     }
 
+    override fun onDismiss(dialog: DialogInterface) {
+        super.onDismiss(dialog)
+        targetFragment?.onActivityResult(targetRequestCode, arguments.getInt("key"), Intent())
+    }
+
     protected abstract fun checkLogin()
 
     protected abstract fun setCredentialsOnView(view: View)

+ 34 - 0
app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginPreference.kt

@@ -0,0 +1,34 @@
+package eu.kanade.tachiyomi.widget.preference
+
+import android.content.Context
+import android.support.v4.content.ContextCompat
+import android.support.v7.preference.Preference
+import android.support.v7.preference.PreferenceViewHolder
+import android.util.AttributeSet
+import eu.kanade.tachiyomi.R
+import kotlinx.android.synthetic.main.preference_widget_imageview.view.*
+
+class LoginPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+        Preference(context, attrs) {
+
+    init {
+        widgetLayoutResource = R.layout.preference_widget_imageview
+    }
+
+    override fun onBindViewHolder(holder: PreferenceViewHolder) {
+        super.onBindViewHolder(holder)
+
+        with(holder.itemView.image_view) {
+            if (getPersistedString("").isNullOrEmpty()) {
+                setImageResource(android.R.color.transparent)
+            } else {
+                setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_done_green_24dp))
+            }
+        }
+    }
+
+    override public fun notifyChanged() {
+        super.notifyChanged()
+    }
+
+}

+ 4 - 8
app/src/main/java/eu/kanade/tachiyomi/widget/preference/MangaSyncLoginDialog.kt

@@ -1,8 +1,6 @@
 package eu.kanade.tachiyomi.widget.preference
 
-import android.content.DialogInterface
 import android.os.Bundle
-import android.support.v7.preference.Preference
 import android.view.View
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService
@@ -16,10 +14,10 @@ class MangaSyncLoginDialog : LoginDialogPreference() {
 
     companion object {
 
-        fun newInstance(preference: Preference): LoginDialogPreference {
+        fun newInstance(sync: MangaSyncService): LoginDialogPreference {
             val fragment = MangaSyncLoginDialog()
             val bundle = Bundle(1)
-            bundle.putString("key", preference.key)
+            bundle.putInt("key", sync.id)
             fragment.arguments = bundle
             return fragment
         }
@@ -30,12 +28,12 @@ class MangaSyncLoginDialog : LoginDialogPreference() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
-        val syncId = Integer.parseInt(arguments.getString("key"))
+        val syncId = arguments.getInt("key")
         sync = (activity as SettingsActivity).syncManager.getService(syncId)
     }
 
     override fun setCredentialsOnView(view: View) = with(view) {
-        accounts_login.text = getString(R.string.accounts_login_title, sync.name)
+        title.text = getString(R.string.login_title, sync.name)
         username.setText(preferences.getMangaSyncUsername(sync))
         password.setText(preferences.getMangaSyncPassword(sync))
     }
@@ -58,8 +56,6 @@ class MangaSyncLoginDialog : LoginDialogPreference() {
                                     username.text.toString(),
                                     password.text.toString())
 
-                            // Simulate a positive button click and dismiss the dialog
-                            onClick(dialog, DialogInterface.BUTTON_POSITIVE)
                             dialog.dismiss()
                             context.toast(R.string.login_success)
                         } else {

+ 4 - 8
app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt

@@ -1,8 +1,6 @@
 package eu.kanade.tachiyomi.widget.preference
 
-import android.content.DialogInterface
 import android.os.Bundle
-import android.support.v7.preference.Preference
 import android.view.View
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.source.base.Source
@@ -16,10 +14,10 @@ class SourceLoginDialog : LoginDialogPreference() {
 
     companion object {
 
-        fun newInstance(preference: Preference): LoginDialogPreference {
+        fun newInstance(source: Source): LoginDialogPreference {
             val fragment = SourceLoginDialog()
             val bundle = Bundle(1)
-            bundle.putString("key", preference.key)
+            bundle.putInt("key", source.id)
             fragment.arguments = bundle
             return fragment
         }
@@ -30,12 +28,12 @@ class SourceLoginDialog : LoginDialogPreference() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
-        val sourceId = Integer.parseInt(arguments.getString("key"))
+        val sourceId = arguments.getInt("key")
         source = (activity as SettingsActivity).sourceManager.get(sourceId)!!
     }
 
     override fun setCredentialsOnView(view: View) = with(view) {
-        accounts_login.text = getString(R.string.accounts_login_title, source.name)
+        title.text = getString(R.string.login_title, source.visibleName)
         username.setText(preferences.getSourceUsername(source))
         password.setText(preferences.getSourcePassword(source))
     }
@@ -58,8 +56,6 @@ class SourceLoginDialog : LoginDialogPreference() {
                                     username.text.toString(),
                                     password.text.toString())
 
-                            // Simulate a positive button click and dismiss the dialog
-                            onClick(dialog, DialogInterface.BUTTON_POSITIVE)
                             dialog.dismiss()
                             context.toast(R.string.login_success)
                         } else {

BIN
app/src/main/res/drawable-xhdpi/bkg_shadow_img.9.png


+ 9 - 0
app/src/main/res/drawable/ic_done_green_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="#FF4CAF50"
+        android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
+</vector>

+ 1 - 1
app/src/main/res/layout/item_chapter.xml

@@ -96,7 +96,7 @@
             android:layout_alignParentTop="true"
             android:background="?android:selectableItemBackground"
             app:srcCompat="@drawable/ic_more_horiz_black_24dp"/>
-        
+
     </RelativeLayout>
 
 

+ 2 - 2
app/src/main/res/layout/pref_account_login.xml

@@ -9,8 +9,8 @@
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:text="@string/accounts_login_title"
-        android:id="@+id/accounts_login"
+        android:text="@string/login_title"
+        android:id="@+id/title"
         android:textStyle="bold"
         android:textSize="16sp"
         android:layout_gravity="center_horizontal" />

+ 7 - 0
app/src/main/res/layout/preference_widget_imageview.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+           android:id="@+id/image_view"
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content">
+
+</ImageView>

+ 4 - 1
app/src/main/res/values/keys.xml

@@ -2,10 +2,11 @@
 <resources>
     <string name="pref_category_general_key">pref_category_general_key</string>
     <string name="pref_category_reader_key">pref_category_reader_key</string>
-    <string name="pref_category_accounts_key">pref_category_accounts_key</string>
+    <string name="pref_category_sync_key">pref_category_sync_key</string>
     <string name="pref_category_downloads_key">pref_category_downloads_key</string>
     <string name="pref_category_advanced_key">pref_category_advanced_key</string>
     <string name="pref_category_about_key">pref_category_about_key</string>
+    <string name="pref_category_sources_key">pref_category_sources_key</string>
 
     <string name="pref_library_columns_dialog_key">pref_library_columns_dialog_key</string>
     <string name="pref_library_columns_portrait_key">pref_library_columns_portrait_key</string>
@@ -35,6 +36,8 @@
     <string name="pref_download_slots_key">pref_download_slots_key</string>
     <string name="pref_download_only_over_wifi_key">pref_download_only_over_wifi_key</string>
 
+    <string name="pref_source_languages">pref_source_languages</string>
+
     <string name="pref_clear_chapter_cache_key">pref_clear_chapter_cache_key</string>
     <string name="pref_clear_database_key">pref_clear_database_key</string>
 

+ 11 - 3
app/src/main/res/values/strings.xml

@@ -63,8 +63,9 @@
       <!-- Subsections -->
     <string name="pref_category_general">General</string>
     <string name="pref_category_reader">Reader</string>
-    <string name="pref_category_accounts">Accounts</string>
     <string name="pref_category_downloads">Downloads</string>
+    <string name="pref_category_sources">Sources</string>
+    <string name="pref_category_sync">Sync</string>
     <string name="pref_category_advanced">Advanced</string>
     <string name="pref_category_about">About</string>
 
@@ -124,13 +125,20 @@
     <string name="rotation_force_portrait">Force portrait</string>
     <string name="rotation_force_landscape">Force landscape</string>
 
-
       <!-- Downloads section -->
     <string name="pref_download_directory">Downloads directory</string>
     <string name="pref_download_slots">Simultaneous downloads</string>
     <string name="pref_download_only_over_wifi">Only download over Wi-Fi</string>
     <string name="custom_dir">Custom directory</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>
+
       <!-- Advanced section -->
     <string name="pref_clear_chapter_cache">Clear chapter cache</string>
     <string name="used_cache">Used: %1$s</string>
@@ -150,7 +158,7 @@
 
 
     <!-- Login dialog -->
-    <string name="accounts_login_title">Login for %1$s</string>
+    <string name="login_title">Login for %1$s</string>
     <string name="username">Username</string>
     <string name="password">Password</string>
     <string name="show_password">Show password</string>

+ 0 - 16
app/src/main/res/xml/pref_accounts.xml

@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.v7.preference.PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orderingFromXml="true">
-
-    <PreferenceCategory
-        android:key="pref_category_source_accounts"
-        android:title="Sources"
-        android:persistent="false"/>
-
-    <PreferenceCategory
-        android:key="pref_category_manga_sync_accounts"
-        android:title="Sync"
-        android:persistent="false"/>
-
-</android.support.v7.preference.PreferenceScreen>

+ 0 - 11
app/src/main/res/xml/pref_general.xml

@@ -21,15 +21,4 @@
         android:title="@string/pref_update_only_non_completed"
         android:defaultValue="false"/>
 
-    <SwitchPreferenceCompat
-        android:key="@string/pref_auto_update_manga_sync_key"
-        android:title="@string/pref_auto_update_manga_sync"
-        android:defaultValue="true"/>
-
-    <SwitchPreferenceCompat
-        android:key="@string/pref_ask_update_manga_sync_key"
-        android:title="@string/pref_ask_update_manga_sync"
-        android:defaultValue="false"
-        android:dependency="@string/pref_auto_update_manga_sync_key"/>
-
 </android.support.v7.preference.PreferenceScreen>

+ 7 - 2
app/src/main/res/xml/pref_main.xml

@@ -16,11 +16,16 @@
         android:key="@string/pref_category_downloads_key"
         android:persistent="false"
         android:title="@string/pref_category_downloads" />
+
+    <Preference
+        android:key="@string/pref_category_sources_key"
+        android:persistent="false"
+        android:title="@string/pref_category_sources" />
     
     <Preference
-        android:key="@string/pref_category_accounts_key"
+        android:key="@string/pref_category_sync_key"
         android:persistent="false"
-        android:title="@string/pref_category_accounts" />
+        android:title="@string/pref_category_sync" />
 
     <Preference
         android:key="@string/pref_category_advanced_key"

+ 16 - 0
app/src/main/res/xml/pref_sources.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.preference.PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orderingFromXml="true">
+
+    <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"/>
+
+</android.support.v7.preference.PreferenceScreen>

+ 22 - 0
app/src/main/res/xml/pref_sync.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.preference.PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orderingFromXml="true">
+
+    <SwitchPreferenceCompat
+        android:key="@string/pref_auto_update_manga_sync_key"
+        android:title="@string/pref_auto_update_manga_sync"
+        android:defaultValue="true"/>
+
+    <SwitchPreferenceCompat
+        android:key="@string/pref_ask_update_manga_sync_key"
+        android:title="@string/pref_ask_update_manga_sync"
+        android:defaultValue="false"
+        android:dependency="@string/pref_auto_update_manga_sync_key"/>
+
+    <PreferenceCategory
+        android:key="pref_category_manga_sync_accounts"
+        android:title="@string/services"
+        android:persistent="false"/>
+
+</android.support.v7.preference.PreferenceScreen>