瀏覽代碼

Preferences with conductor (#792)

* Settings with conductor WIP

* Add downloads preference controller. Implement source/track login

* Improve settings controllers

* Backup settings controller

* Delete preferences xml

* Remove keys from xml

* PreferenceKeys is now an object

* Remove now unused dependency
inorichi 7 年之前
父節點
當前提交
ff190e02d4
共有 51 個文件被更改,包括 1969 次插入2091 次删除
  1. 1 1
      app/build.gradle
  2. 0 4
      app/src/main/AndroidManifest.xml
  3. 23 0
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt
  4. 8 11
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt
  5. 1 1
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt
  6. 0 1
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
  7. 18 25
      app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt
  8. 114 120
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
  9. 58 59
      app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
  10. 0 10
      app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt
  11. 12 0
      app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt
  12. 5 25
      app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
  13. 1 0
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt
  14. 2 1
      app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt
  15. 2 1
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt
  16. 102 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt
  17. 166 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt
  18. 0 139
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt
  19. 0 86
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt
  20. 159 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
  21. 0 117
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt
  22. 458 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt
  23. 0 407
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupFragment.kt
  24. 70 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt
  25. 186 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt
  26. 0 149
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadsFragment.kt
  27. 0 62
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt
  28. 225 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt
  29. 0 166
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt
  30. 70 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt
  31. 106 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt
  32. 25 45
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt
  33. 91 0
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt
  34. 0 95
      app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingFragment.kt
  35. 8 1
      app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt
  36. 1 1
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt
  37. 14 15
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt
  38. 16 24
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt
  39. 6 8
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt
  40. 19 27
      app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt
  41. 2 0
      app/src/main/res/layout/pref_library_columns.xml
  42. 0 76
      app/src/main/res/values/keys.xml
  43. 0 4
      app/src/main/res/values/themes.xml
  44. 0 37
      app/src/main/res/xml/pref_about.xml
  45. 0 33
      app/src/main/res/xml/pref_advanced.xml
  46. 0 48
      app/src/main/res/xml/pref_backup.xml
  47. 0 62
      app/src/main/res/xml/pref_downloads.xml
  48. 0 76
      app/src/main/res/xml/pref_general.xml
  49. 0 103
      app/src/main/res/xml/pref_reader.xml
  50. 0 18
      app/src/main/res/xml/pref_sources.xml
  51. 0 33
      app/src/main/res/xml/pref_tracking.xml

+ 1 - 1
app/build.gradle

@@ -101,6 +101,7 @@ android {
 dependencies {
 
     compile "com.bluelinelabs:conductor:2.1.3"
+    compile 'com.github.inorichi:conductor-support-preference:master-SNAPSHOT'
 
     final rxbindings_version = '1.0.1'
     compile "com.jakewharton.rxbinding:rxbinding-kotlin:$rxbindings_version"
@@ -204,7 +205,6 @@ dependencies {
     compile 'com.nononsenseapps:filepicker:2.5.2'
     compile 'com.github.amulyakhare:TextDrawable:558677e'
     compile 'com.afollestad.material-dialogs:core:0.9.4.2'
-    compile 'net.xpece.android:support-preference:1.2.5'
     compile 'me.zhanghai.android.systemuihelper:library:1.0.0'
     compile 'de.hdodenhof:circleimageview:2.1.0'
 

+ 0 - 4
app/src/main/AndroidManifest.xml

@@ -35,10 +35,6 @@
         <activity
             android:name=".ui.reader.ReaderActivity"
             android:theme="@style/Theme.Reader" />
-        <activity
-            android:name=".ui.setting.SettingsActivity"
-            android:label="@string/label_settings"
-            android:parentActivityName=".ui.main.MainActivity" />
         <activity
             android:name=".widget.CustomLayoutPickerActivity"
             android:label="@string/app_name"

+ 23 - 0
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt

@@ -0,0 +1,23 @@
+package eu.kanade.tachiyomi.data.backup
+import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
+
+
+object BackupConst {
+
+    const val INTENT_FILTER = "SettingsBackupFragment"
+    const val ACTION_BACKUP_COMPLETED_DIALOG = "$ID.$INTENT_FILTER.ACTION_BACKUP_COMPLETED_DIALOG"
+    const val ACTION_SET_PROGRESS_DIALOG = "$ID.$INTENT_FILTER.ACTION_SET_PROGRESS_DIALOG"
+    const val ACTION_ERROR_BACKUP_DIALOG = "$ID.$INTENT_FILTER.ACTION_ERROR_BACKUP_DIALOG"
+    const val ACTION_ERROR_RESTORE_DIALOG = "$ID.$INTENT_FILTER.ACTION_ERROR_RESTORE_DIALOG"
+    const val ACTION_RESTORE_COMPLETED_DIALOG = "$ID.$INTENT_FILTER.ACTION_RESTORE_COMPLETED_DIALOG"
+    const val ACTION = "$ID.$INTENT_FILTER.ACTION"
+    const val EXTRA_PROGRESS = "$ID.$INTENT_FILTER.EXTRA_PROGRESS"
+    const val EXTRA_AMOUNT = "$ID.$INTENT_FILTER.EXTRA_AMOUNT"
+    const val EXTRA_ERRORS = "$ID.$INTENT_FILTER.EXTRA_ERRORS"
+    const val EXTRA_CONTENT = "$ID.$INTENT_FILTER.EXTRA_CONTENT"
+    const val EXTRA_ERROR_MESSAGE = "$ID.$INTENT_FILTER.EXTRA_ERROR_MESSAGE"
+    const val EXTRA_URI = "$ID.$INTENT_FILTER.EXTRA_URI"
+    const val EXTRA_TIME = "$ID.$INTENT_FILTER.EXTRA_TIME"
+    const val EXTRA_ERROR_FILE_PATH = "$ID.$INTENT_FILTER.EXTRA_ERROR_FILE_PATH"
+    const val EXTRA_ERROR_FILE = "$ID.$INTENT_FILTER.EXTRA_ERROR_FILE"
+}

+ 8 - 11
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt

@@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
 import eu.kanade.tachiyomi.data.backup.models.Backup.MANGAS
 import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
 import eu.kanade.tachiyomi.data.database.models.Manga
-import eu.kanade.tachiyomi.ui.setting.SettingsBackupFragment
 import eu.kanade.tachiyomi.util.AndroidComponentUtil
 import eu.kanade.tachiyomi.util.sendLocalBroadcast
 import timber.log.Timber
@@ -28,8 +27,6 @@ class BackupCreateService : IntentService(NAME) {
         // Name of class
         private const val NAME = "BackupCreateService"
 
-        // Uri as string
-        private const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
         // Backup called from job
         private const val EXTRA_IS_JOB = "$ID.$NAME.EXTRA_IS_JOB"
         // Options for backup
@@ -56,7 +53,7 @@ class BackupCreateService : IntentService(NAME) {
          */
         fun makeBackup(context: Context, path: String, flags: Int, isJob: Boolean = false) {
             val intent = Intent(context, BackupCreateService::class.java).apply {
-                putExtra(EXTRA_URI, path)
+                putExtra(BackupConst.EXTRA_URI, path)
                 putExtra(EXTRA_IS_JOB, isJob)
                 putExtra(EXTRA_FLAGS, flags)
             }
@@ -74,7 +71,7 @@ class BackupCreateService : IntentService(NAME) {
         if (intent == null) return
 
         // Get values
-        val uri = intent.getStringExtra(EXTRA_URI)
+        val uri = intent.getStringExtra(BackupConst.EXTRA_URI)
         val isJob = intent.getBooleanExtra(EXTRA_IS_JOB, false)
         val flags = intent.getIntExtra(EXTRA_FLAGS, 0)
         // Create backup
@@ -150,9 +147,9 @@ class BackupCreateService : IntentService(NAME) {
                 }
 
                 // Show completed dialog
-                val intent = Intent(SettingsBackupFragment.INTENT_FILTER).apply {
-                    putExtra(SettingsBackupFragment.ACTION, SettingsBackupFragment.ACTION_BACKUP_COMPLETED_DIALOG)
-                    putExtra(SettingsBackupFragment.EXTRA_URI, file.uri.toString())
+                val intent = Intent(BackupConst.INTENT_FILTER).apply {
+                    putExtra(BackupConst.ACTION, BackupConst.ACTION_BACKUP_COMPLETED_DIALOG)
+                    putExtra(BackupConst.EXTRA_URI, file.uri.toString())
                 }
                 sendLocalBroadcast(intent)
             }
@@ -160,9 +157,9 @@ class BackupCreateService : IntentService(NAME) {
             Timber.e(e)
             if (!isJob) {
                 // Show error dialog
-                val intent = Intent(SettingsBackupFragment.INTENT_FILTER).apply {
-                    putExtra(SettingsBackupFragment.ACTION, SettingsBackupFragment.ACTION_ERROR_BACKUP_DIALOG)
-                    putExtra(SettingsBackupFragment.EXTRA_ERROR_MESSAGE, e.message)
+                val intent = Intent(BackupConst.INTENT_FILTER).apply {
+                    putExtra(BackupConst.ACTION, BackupConst.ACTION_ERROR_BACKUP_DIALOG)
+                    putExtra(BackupConst.EXTRA_ERROR_MESSAGE, e.message)
                 }
                 sendLocalBroadcast(intent)
             }

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt

@@ -14,7 +14,7 @@ class BackupCreatorJob : Job() {
         val preferences = Injekt.get<PreferencesHelper>()
         val path = preferences.backupsDirectory().getOrDefault()
         val flags = BackupCreateService.BACKUP_ALL
-        BackupCreateService.makeBackup(context,path,flags,true)
+        BackupCreateService.makeBackup(context, path, flags, true)
         return Result.SUCCESS
     }
 

+ 0 - 1
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt

@@ -28,7 +28,6 @@ import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.util.syncChaptersWithSource
 import rx.Observable
 import uy.kohesive.injekt.injectLazy
-import java.util.*
 
 class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
 

+ 18 - 25
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt

@@ -22,7 +22,6 @@ import eu.kanade.tachiyomi.data.backup.models.DHistory
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
 import eu.kanade.tachiyomi.data.database.models.*
 import eu.kanade.tachiyomi.source.Source
-import eu.kanade.tachiyomi.ui.setting.SettingsBackupFragment
 import eu.kanade.tachiyomi.util.AndroidComponentUtil
 import eu.kanade.tachiyomi.util.chop
 import eu.kanade.tachiyomi.util.sendLocalBroadcast
@@ -36,7 +35,6 @@ import java.text.SimpleDateFormat
 import java.util.*
 import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
-import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
 
 /**
  * Restores backup from json file
@@ -44,11 +42,6 @@ import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
 class BackupRestoreService : Service() {
 
     companion object {
-        // Name of service
-        private const val NAME = "BackupRestoreService"
-
-        // Uri as string
-        private const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
 
         /**
          * Returns the status of the service.
@@ -69,7 +62,7 @@ class BackupRestoreService : Service() {
         fun start(context: Context, uri: Uri) {
             if (!isRunning(context)) {
                 val intent = Intent(context, BackupRestoreService::class.java).apply {
-                    putExtra(EXTRA_URI, uri)
+                    putExtra(BackupConst.EXTRA_URI, uri)
                 }
                 context.startService(intent)
             }
@@ -164,7 +157,7 @@ class BackupRestoreService : Service() {
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
         if (intent == null) return Service.START_NOT_STICKY
 
-        val uri = intent.getParcelableExtra<Uri>(EXTRA_URI)
+        val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
 
         // Unsubscribe from any previous subscription if needed.
         subscription?.unsubscribe()
@@ -236,12 +229,12 @@ class BackupRestoreService : Service() {
                     val endTime = System.currentTimeMillis()
                     val time = endTime - startTime
                     val logFile = writeErrorLog()
-                    val completeIntent = Intent(SettingsBackupFragment.INTENT_FILTER).apply {
-                        putExtra(SettingsBackupFragment.EXTRA_TIME, time)
-                        putExtra(SettingsBackupFragment.EXTRA_ERRORS, errors.size)
-                        putExtra(SettingsBackupFragment.EXTRA_ERROR_FILE_PATH, logFile.parent)
-                        putExtra(SettingsBackupFragment.EXTRA_ERROR_FILE, logFile.name)
-                        putExtra(SettingsBackupFragment.ACTION, SettingsBackupFragment.ACTION_RESTORE_COMPLETED_DIALOG)
+                    val completeIntent = Intent(BackupConst.INTENT_FILTER).apply {
+                        putExtra(BackupConst.EXTRA_TIME, time)
+                        putExtra(BackupConst.EXTRA_ERRORS, errors.size)
+                        putExtra(BackupConst.EXTRA_ERROR_FILE_PATH, logFile.parent)
+                        putExtra(BackupConst.EXTRA_ERROR_FILE, logFile.name)
+                        putExtra(BackupConst.ACTION, BackupConst.ACTION_RESTORE_COMPLETED_DIALOG)
                     }
                     sendLocalBroadcast(completeIntent)
 
@@ -249,9 +242,9 @@ class BackupRestoreService : Service() {
                 .doOnError { error ->
                     Timber.e(error)
                     writeErrorLog()
-                    val errorIntent = Intent(SettingsBackupFragment.INTENT_FILTER).apply {
-                        putExtra(SettingsBackupFragment.ACTION, SettingsBackupFragment.ACTION_ERROR_RESTORE_DIALOG)
-                        putExtra(SettingsBackupFragment.EXTRA_ERROR_MESSAGE, error.message)
+                    val errorIntent = Intent(BackupConst.INTENT_FILTER).apply {
+                        putExtra(BackupConst.ACTION, BackupConst.ACTION_ERROR_RESTORE_DIALOG)
+                        putExtra(BackupConst.EXTRA_ERROR_MESSAGE, error.message)
                     }
                     sendLocalBroadcast(errorIntent)
                 }
@@ -392,7 +385,7 @@ class BackupRestoreService : Service() {
 
 
     /**
-     * Called to update dialog in [SettingsBackupFragment]
+     * Called to update dialog in [BackupConst]
      *
      * @param progress restore progress
      * @param amount total restoreAmount of manga
@@ -400,12 +393,12 @@ class BackupRestoreService : Service() {
      */
     private fun showRestoreProgress(progress: Int, amount: Int, title: String, errors: Int,
                                     content: String = getString(R.string.dialog_restoring_backup, title.chop(15))) {
-        val intent = Intent(SettingsBackupFragment.INTENT_FILTER).apply {
-            putExtra(SettingsBackupFragment.EXTRA_PROGRESS, progress)
-            putExtra(SettingsBackupFragment.EXTRA_AMOUNT, amount)
-            putExtra(SettingsBackupFragment.EXTRA_CONTENT, content)
-            putExtra(SettingsBackupFragment.EXTRA_ERRORS, errors)
-            putExtra(SettingsBackupFragment.ACTION, SettingsBackupFragment.ACTION_SET_PROGRESS_DIALOG)
+        val intent = Intent(BackupConst.INTENT_FILTER).apply {
+            putExtra(BackupConst.EXTRA_PROGRESS, progress)
+            putExtra(BackupConst.EXTRA_AMOUNT, amount)
+            putExtra(BackupConst.EXTRA_CONTENT, content)
+            putExtra(BackupConst.EXTRA_ERRORS, errors)
+            putExtra(BackupConst.ACTION, BackupConst.ACTION_SET_PROGRESS_DIALOG)
         }
         sendLocalBroadcast(intent)
     }

+ 114 - 120
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt

@@ -1,120 +1,114 @@
-package eu.kanade.tachiyomi.data.preference
-
-import android.content.Context
-import eu.kanade.tachiyomi.R
-
-/**
- * This class stores the keys for the preferences in the application. Most of them are defined
- * in the file "keys.xml". By using this class we can define preferences in one place and get them
- * referenced here.
- */
-@Suppress("HasPlatformType")
-class PreferenceKeys(context: Context) {
-
-    val theme = context.getString(R.string.pref_theme_key)
-
-    val rotation = context.getString(R.string.pref_rotation_type_key)
-
-    val enableTransitions = context.getString(R.string.pref_enable_transitions_key)
-
-    val showPageNumber = context.getString(R.string.pref_show_page_number_key)
-
-    val fullscreen = context.getString(R.string.pref_fullscreen_key)
-
-    val keepScreenOn = context.getString(R.string.pref_keep_screen_on_key)
-
-    val customBrightness = context.getString(R.string.pref_custom_brightness_key)
-
-    val customBrightnessValue = context.getString(R.string.pref_custom_brightness_value_key)
-
-    val colorFilter = context.getString(R.string.pref_color_filter_key)
-
-    val colorFilterValue = context.getString(R.string.pref_color_filter_value_key)
-
-    val defaultViewer = context.getString(R.string.pref_default_viewer_key)
-
-    val imageScaleType = context.getString(R.string.pref_image_scale_type_key)
-
-    val imageDecoder = context.getString(R.string.pref_image_decoder_key)
-
-    val zoomStart = context.getString(R.string.pref_zoom_start_key)
-
-    val readerTheme = context.getString(R.string.pref_reader_theme_key)
-
-    val cropBorders = context.getString(R.string.pref_crop_borders_key)
-
-    val readWithTapping = context.getString(R.string.pref_read_with_tapping_key)
-
-    val readWithVolumeKeys = context.getString(R.string.pref_read_with_volume_keys_key)
-
-    val portraitColumns = context.getString(R.string.pref_library_columns_portrait_key)
-
-    val landscapeColumns = context.getString(R.string.pref_library_columns_landscape_key)
-
-    val updateOnlyNonCompleted = context.getString(R.string.pref_update_only_non_completed_key)
-
-    val autoUpdateTrack = context.getString(R.string.pref_auto_update_manga_sync_key)
-
-    val askUpdateTrack = context.getString(R.string.pref_ask_update_manga_sync_key)
-
-    val lastUsedCatalogueSource = context.getString(R.string.pref_last_catalogue_source_key)
-
-    val lastUsedCategory = context.getString(R.string.pref_last_used_category_key)
-
-    val catalogueAsList = context.getString(R.string.pref_display_catalogue_as_list)
-
-    val enabledLanguages = context.getString(R.string.pref_source_languages)
-
-    val backupDirectory = context.getString(R.string.pref_backup_directory_key)
-
-    val downloadsDirectory = context.getString(R.string.pref_download_directory_key)
-
-    val downloadThreads = context.getString(R.string.pref_download_slots_key)
-
-    val downloadOnlyOverWifi = context.getString(R.string.pref_download_only_over_wifi_key)
-
-    val numberOfBackups = context.getString(R.string.pref_backup_slots_key)
-
-    val backupInterval = context.getString(R.string.pref_backup_interval_key)
-
-    val removeAfterReadSlots = context.getString(R.string.pref_remove_after_read_slots_key)
-
-    val removeAfterMarkedAsRead = context.getString(R.string.pref_remove_after_marked_as_read_key)
-
-    val libraryUpdateInterval = context.getString(R.string.pref_library_update_interval_key)
-
-    val libraryUpdateRestriction = context.getString(R.string.pref_library_update_restriction_key)
-
-    val libraryUpdateCategories = context.getString(R.string.pref_library_update_categories_key)
-
-    val filterDownloaded = context.getString(R.string.pref_filter_downloaded_key)
-
-    val filterUnread = context.getString(R.string.pref_filter_unread_key)
-
-    val librarySortingMode = context.getString(R.string.pref_library_sorting_mode_key)
-
-    val automaticUpdates = context.getString(R.string.pref_enable_automatic_updates_key)
-
-    val startScreen = context.getString(R.string.pref_start_screen_key)
-
-    val downloadNew = context.getString(R.string.pref_download_new_key)
-
-    val downloadNewCategories = context.getString(R.string.pref_download_new_categories_key)
-
-    fun sourceUsername(sourceId: Long) = "pref_source_username_$sourceId"
-
-    fun sourcePassword(sourceId: Long) = "pref_source_password_$sourceId"
-
-    fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
-
-    fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"
-
-    fun trackToken(syncId: Int) = "track_token_$syncId"
-
-    val libraryAsList = context.getString(R.string.pref_display_library_as_list)
-
-    val lang = context.getString(R.string.pref_language_key)
-
-    val defaultCategory = context.getString(R.string.default_category_key)
-
-}
+package eu.kanade.tachiyomi.data.preference
+
+/**
+ * This class stores the keys for the preferences in the application.
+ */
+object PreferenceKeys {
+
+    const val theme = "pref_theme_key"
+
+    const val rotation = "pref_rotation_type_key"
+
+    const val enableTransitions = "pref_enable_transitions_key"
+
+    const val showPageNumber = "pref_show_page_number_key"
+
+    const val fullscreen = "fullscreen"
+
+    const val keepScreenOn = "pref_keep_screen_on_key"
+
+    const val customBrightness = "pref_custom_brightness_key"
+
+    const val customBrightnessValue = "custom_brightness_value"
+
+    const val colorFilter = "pref_color_filter_key"
+
+    const val colorFilterValue = "color_filter_value"
+
+    const val defaultViewer = "pref_default_viewer_key"
+
+    const val imageScaleType = "pref_image_scale_type_key"
+
+    const val imageDecoder = "image_decoder"
+
+    const val zoomStart = "pref_zoom_start_key"
+
+    const val readerTheme = "pref_reader_theme_key"
+
+    const val cropBorders = "crop_borders"
+
+    const val readWithTapping = "reader_tap"
+
+    const val readWithVolumeKeys = "reader_volume_keys"
+
+    const val portraitColumns = "pref_library_columns_portrait_key"
+
+    const val landscapeColumns = "pref_library_columns_landscape_key"
+
+    const val updateOnlyNonCompleted = "pref_update_only_non_completed_key"
+
+    const val autoUpdateTrack = "pref_auto_update_manga_sync_key"
+
+    const val askUpdateTrack = "pref_ask_update_manga_sync_key"
+
+    const val lastUsedCatalogueSource = "last_catalogue_source"
+
+    const val lastUsedCategory = "last_used_category"
+
+    const val catalogueAsList = "pref_display_catalogue_as_list"
+
+    const val enabledLanguages = "source_languages"
+
+    const val backupDirectory = "backup_directory"
+
+    const val downloadsDirectory = "download_directory"
+
+    const val downloadThreads = "pref_download_slots_key"
+
+    const val downloadOnlyOverWifi = "pref_download_only_over_wifi_key"
+
+    const val numberOfBackups = "backup_slots"
+
+    const val backupInterval = "backup_interval"
+
+    const val removeAfterReadSlots = "remove_after_read_slots"
+
+    const val removeAfterMarkedAsRead = "pref_remove_after_marked_as_read_key"
+
+    const val libraryUpdateInterval = "pref_library_update_interval_key"
+
+    const val libraryUpdateRestriction = "library_update_restriction"
+
+    const val libraryUpdateCategories = "library_update_categories"
+
+    const val filterDownloaded = "pref_filter_downloaded_key"
+
+    const val filterUnread = "pref_filter_unread_key"
+
+    const val librarySortingMode = "library_sorting_mode"
+
+    const val automaticUpdates = "automatic_updates"
+
+    const val startScreen = "start_screen"
+
+    const val downloadNew = "download_new"
+
+    const val downloadNewCategories = "download_new_categories"
+
+    const val libraryAsList = "pref_display_library_as_list"
+
+    const val lang = "app_language"
+
+    const val defaultCategory = "default_category"
+
+    fun sourceUsername(sourceId: Long) = "pref_source_username_$sourceId"
+
+    fun sourcePassword(sourceId: Long) = "pref_source_password_$sourceId"
+
+    fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
+
+    fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"
+
+    fun trackToken(syncId: Int) = "track_token_$syncId"
+
+}

+ 58 - 59
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.track.TrackService
 import eu.kanade.tachiyomi.source.Source
 import java.io.File
+import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
 
 fun <T> Preference<T>.getOrDefault(): T = get() ?: defaultValue()!!
 
@@ -17,8 +18,6 @@ fun Preference<Boolean>.invert(): Boolean = getOrDefault().let { set(!it); !it }
 
 class PreferencesHelper(val context: Context) {
 
-    val keys = PreferenceKeys(context)
-
     private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
     private val rxPrefs = RxSharedPreferences.create(prefs)
 
@@ -30,134 +29,134 @@ class PreferencesHelper(val context: Context) {
             File(Environment.getExternalStorageDirectory().absolutePath + File.separator +
                     context.getString(R.string.app_name), "backup"))
 
-    fun startScreen() = prefs.getInt(keys.startScreen, 1)
+    fun startScreen() = prefs.getInt(Keys.startScreen, 1)
 
     fun clear() = prefs.edit().clear().apply()
 
-    fun theme() = prefs.getInt(keys.theme, 1)
+    fun theme() = prefs.getInt(Keys.theme, 1)
 
-    fun rotation() = rxPrefs.getInteger(keys.rotation, 1)
+    fun rotation() = rxPrefs.getInteger(Keys.rotation, 1)
 
-    fun pageTransitions() = rxPrefs.getBoolean(keys.enableTransitions, true)
+    fun pageTransitions() = rxPrefs.getBoolean(Keys.enableTransitions, true)
 
-    fun showPageNumber() = rxPrefs.getBoolean(keys.showPageNumber, true)
+    fun showPageNumber() = rxPrefs.getBoolean(Keys.showPageNumber, true)
 
-    fun fullscreen() = rxPrefs.getBoolean(keys.fullscreen, true)
+    fun fullscreen() = rxPrefs.getBoolean(Keys.fullscreen, true)
 
-    fun keepScreenOn() = rxPrefs.getBoolean(keys.keepScreenOn, true)
+    fun keepScreenOn() = rxPrefs.getBoolean(Keys.keepScreenOn, true)
 
-    fun customBrightness() = rxPrefs.getBoolean(keys.customBrightness, false)
+    fun customBrightness() = rxPrefs.getBoolean(Keys.customBrightness, false)
 
-    fun customBrightnessValue() = rxPrefs.getInteger(keys.customBrightnessValue, 0)
+    fun customBrightnessValue() = rxPrefs.getInteger(Keys.customBrightnessValue, 0)
 
-    fun colorFilter() = rxPrefs.getBoolean(keys.colorFilter, false)
+    fun colorFilter() = rxPrefs.getBoolean(Keys.colorFilter, false)
 
-    fun colorFilterValue() = rxPrefs.getInteger(keys.colorFilterValue, 0)
+    fun colorFilterValue() = rxPrefs.getInteger(Keys.colorFilterValue, 0)
 
-    fun defaultViewer() = prefs.getInt(keys.defaultViewer, 1)
+    fun defaultViewer() = prefs.getInt(Keys.defaultViewer, 1)
 
-    fun imageScaleType() = rxPrefs.getInteger(keys.imageScaleType, 1)
+    fun imageScaleType() = rxPrefs.getInteger(Keys.imageScaleType, 1)
 
-    fun imageDecoder() = rxPrefs.getInteger(keys.imageDecoder, 0)
+    fun imageDecoder() = rxPrefs.getInteger(Keys.imageDecoder, 0)
 
-    fun zoomStart() = rxPrefs.getInteger(keys.zoomStart, 1)
+    fun zoomStart() = rxPrefs.getInteger(Keys.zoomStart, 1)
 
-    fun readerTheme() = rxPrefs.getInteger(keys.readerTheme, 0)
+    fun readerTheme() = rxPrefs.getInteger(Keys.readerTheme, 0)
 
-    fun cropBorders() = rxPrefs.getBoolean(keys.cropBorders, false)
+    fun cropBorders() = rxPrefs.getBoolean(Keys.cropBorders, false)
 
-    fun readWithTapping() = rxPrefs.getBoolean(keys.readWithTapping, true)
+    fun readWithTapping() = rxPrefs.getBoolean(Keys.readWithTapping, true)
 
-    fun readWithVolumeKeys() = rxPrefs.getBoolean(keys.readWithVolumeKeys, false)
+    fun readWithVolumeKeys() = rxPrefs.getBoolean(Keys.readWithVolumeKeys, false)
 
-    fun portraitColumns() = rxPrefs.getInteger(keys.portraitColumns, 0)
+    fun portraitColumns() = rxPrefs.getInteger(Keys.portraitColumns, 0)
 
-    fun landscapeColumns() = rxPrefs.getInteger(keys.landscapeColumns, 0)
+    fun landscapeColumns() = rxPrefs.getInteger(Keys.landscapeColumns, 0)
 
-    fun updateOnlyNonCompleted() = prefs.getBoolean(keys.updateOnlyNonCompleted, false)
+    fun updateOnlyNonCompleted() = prefs.getBoolean(Keys.updateOnlyNonCompleted, false)
 
-    fun autoUpdateTrack() = prefs.getBoolean(keys.autoUpdateTrack, true)
+    fun autoUpdateTrack() = prefs.getBoolean(Keys.autoUpdateTrack, true)
 
-    fun askUpdateTrack() = prefs.getBoolean(keys.askUpdateTrack, false)
+    fun askUpdateTrack() = prefs.getBoolean(Keys.askUpdateTrack, false)
 
-    fun lastUsedCatalogueSource() = rxPrefs.getLong(keys.lastUsedCatalogueSource, -1)
+    fun lastUsedCatalogueSource() = rxPrefs.getLong(Keys.lastUsedCatalogueSource, -1)
 
-    fun lastUsedCategory() = rxPrefs.getInteger(keys.lastUsedCategory, 0)
+    fun lastUsedCategory() = rxPrefs.getInteger(Keys.lastUsedCategory, 0)
 
     fun lastVersionCode() = rxPrefs.getInteger("last_version_code", 0)
 
-    fun catalogueAsList() = rxPrefs.getBoolean(keys.catalogueAsList, false)
+    fun catalogueAsList() = rxPrefs.getBoolean(Keys.catalogueAsList, false)
 
-    fun enabledLanguages() = rxPrefs.getStringSet(keys.enabledLanguages, setOf("en"))
+    fun enabledLanguages() = rxPrefs.getStringSet(Keys.enabledLanguages, setOf("en"))
 
-    fun sourceUsername(source: Source) = prefs.getString(keys.sourceUsername(source.id), "")
+    fun sourceUsername(source: Source) = prefs.getString(Keys.sourceUsername(source.id), "")
 
-    fun sourcePassword(source: Source) = prefs.getString(keys.sourcePassword(source.id), "")
+    fun sourcePassword(source: Source) = prefs.getString(Keys.sourcePassword(source.id), "")
 
     fun setSourceCredentials(source: Source, username: String, password: String) {
         prefs.edit()
-                .putString(keys.sourceUsername(source.id), username)
-                .putString(keys.sourcePassword(source.id), password)
+                .putString(Keys.sourceUsername(source.id), username)
+                .putString(Keys.sourcePassword(source.id), password)
                 .apply()
     }
 
-    fun trackUsername(sync: TrackService) = prefs.getString(keys.trackUsername(sync.id), "")
+    fun trackUsername(sync: TrackService) = prefs.getString(Keys.trackUsername(sync.id), "")
 
-    fun trackPassword(sync: TrackService) = prefs.getString(keys.trackPassword(sync.id), "")
+    fun trackPassword(sync: TrackService) = prefs.getString(Keys.trackPassword(sync.id), "")
 
     fun setTrackCredentials(sync: TrackService, username: String, password: String) {
         prefs.edit()
-                .putString(keys.trackUsername(sync.id), username)
-                .putString(keys.trackPassword(sync.id), password)
+                .putString(Keys.trackUsername(sync.id), username)
+                .putString(Keys.trackPassword(sync.id), password)
                 .apply()
     }
 
-    fun trackToken(sync: TrackService) = rxPrefs.getString(keys.trackToken(sync.id), "")
+    fun trackToken(sync: TrackService) = rxPrefs.getString(Keys.trackToken(sync.id), "")
 
     fun anilistScoreType() = rxPrefs.getInteger("anilist_score_type", 0)
 
-    fun backupsDirectory() = rxPrefs.getString(keys.backupDirectory, defaultBackupDir.toString())
+    fun backupsDirectory() = rxPrefs.getString(Keys.backupDirectory, defaultBackupDir.toString())
 
-    fun downloadsDirectory() = rxPrefs.getString(keys.downloadsDirectory, defaultDownloadsDir.toString())
+    fun downloadsDirectory() = rxPrefs.getString(Keys.downloadsDirectory, defaultDownloadsDir.toString())
 
-    fun downloadThreads() = rxPrefs.getInteger(keys.downloadThreads, 1)
+    fun downloadThreads() = rxPrefs.getInteger(Keys.downloadThreads, 1)
 
-    fun downloadOnlyOverWifi() = prefs.getBoolean(keys.downloadOnlyOverWifi, true)
+    fun downloadOnlyOverWifi() = prefs.getBoolean(Keys.downloadOnlyOverWifi, true)
 
-    fun numberOfBackups() = rxPrefs.getInteger(keys.numberOfBackups, 1)
+    fun numberOfBackups() = rxPrefs.getInteger(Keys.numberOfBackups, 1)
 
-    fun backupInterval() = rxPrefs.getInteger(keys.backupInterval, 0)
+    fun backupInterval() = rxPrefs.getInteger(Keys.backupInterval, 0)
 
-    fun removeAfterReadSlots() = prefs.getInt(keys.removeAfterReadSlots, -1)
+    fun removeAfterReadSlots() = prefs.getInt(Keys.removeAfterReadSlots, -1)
 
-    fun removeAfterMarkedAsRead() = prefs.getBoolean(keys.removeAfterMarkedAsRead, false)
+    fun removeAfterMarkedAsRead() = prefs.getBoolean(Keys.removeAfterMarkedAsRead, false)
 
-    fun libraryUpdateInterval() = rxPrefs.getInteger(keys.libraryUpdateInterval, 0)
+    fun libraryUpdateInterval() = rxPrefs.getInteger(Keys.libraryUpdateInterval, 0)
 
-    fun libraryUpdateRestriction() = prefs.getStringSet(keys.libraryUpdateRestriction, emptySet())
+    fun libraryUpdateRestriction() = prefs.getStringSet(Keys.libraryUpdateRestriction, emptySet())
 
-    fun libraryUpdateCategories() = rxPrefs.getStringSet(keys.libraryUpdateCategories, emptySet())
+    fun libraryUpdateCategories() = rxPrefs.getStringSet(Keys.libraryUpdateCategories, emptySet())
 
-    fun libraryAsList() = rxPrefs.getBoolean(keys.libraryAsList, false)
+    fun libraryAsList() = rxPrefs.getBoolean(Keys.libraryAsList, false)
 
-    fun filterDownloaded() = rxPrefs.getBoolean(keys.filterDownloaded, false)
+    fun filterDownloaded() = rxPrefs.getBoolean(Keys.filterDownloaded, false)
 
-    fun filterUnread() = rxPrefs.getBoolean(keys.filterUnread, false)
+    fun filterUnread() = rxPrefs.getBoolean(Keys.filterUnread, false)
 
-    fun librarySortingMode() = rxPrefs.getInteger(keys.librarySortingMode, 0)
+    fun librarySortingMode() = rxPrefs.getInteger(Keys.librarySortingMode, 0)
 
     fun librarySortingAscending() = rxPrefs.getBoolean("library_sorting_ascending", true)
 
-    fun automaticUpdates() = prefs.getBoolean(keys.automaticUpdates, false)
+    fun automaticUpdates() = prefs.getBoolean(Keys.automaticUpdates, false)
 
     fun hiddenCatalogues() = rxPrefs.getStringSet("hidden_catalogues", emptySet())
 
-    fun downloadNew() = rxPrefs.getBoolean(keys.downloadNew, false)
+    fun downloadNew() = rxPrefs.getBoolean(Keys.downloadNew, false)
 
-    fun downloadNewCategories() = rxPrefs.getStringSet(keys.downloadNewCategories, emptySet())
+    fun downloadNewCategories() = rxPrefs.getStringSet(Keys.downloadNewCategories, emptySet())
 
-    fun lang() = prefs.getString(keys.lang, "")
+    fun lang() = prefs.getString(Keys.lang, "")
 
-    fun defaultCategory() = prefs.getInt(keys.defaultCategory, -1)
+    fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1)
 
 }

+ 0 - 10
app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt

@@ -8,7 +8,6 @@ import android.view.ViewGroup
 import com.bluelinelabs.conductor.ControllerChangeHandler
 import com.bluelinelabs.conductor.ControllerChangeType
 import com.bluelinelabs.conductor.RestoreViewOnCreateController
-import com.bluelinelabs.conductor.Router
 
 abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateController(bundle) {
 
@@ -45,13 +44,4 @@ abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateContr
         (activity as? AppCompatActivity)?.supportActionBar?.title = getTitle()
     }
 
-    fun Router.popControllerWithTag(tag: String): Boolean {
-        val controller = getControllerWithTag(tag)
-        if (controller != null) {
-            popController(controller)
-            return true
-        }
-        return false
-    }
-
 }

+ 12 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt

@@ -0,0 +1,12 @@
+package eu.kanade.tachiyomi.ui.base.controller
+
+import com.bluelinelabs.conductor.Router
+
+fun Router.popControllerWithTag(tag: String): Boolean {
+    val controller = getControllerWithTag(tag)
+    if (controller != null) {
+        popController(controller)
+        return true
+    }
+    return false
+}

+ 5 - 25
app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

@@ -1,7 +1,6 @@
 package eu.kanade.tachiyomi.ui.main
 
 import android.animation.ObjectAnimator
-import android.app.TaskStackBuilder
 import android.content.Intent
 import android.graphics.Color
 import android.os.Bundle
@@ -25,7 +24,7 @@ import eu.kanade.tachiyomi.ui.library.LibraryController
 import eu.kanade.tachiyomi.ui.manga.MangaController
 import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
 import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
-import eu.kanade.tachiyomi.ui.setting.SettingsActivity
+import eu.kanade.tachiyomi.ui.setting.SettingsMainController
 import kotlinx.android.synthetic.main.activity_main.*
 import kotlinx.android.synthetic.main.toolbar.*
 import uy.kohesive.injekt.injectLazy
@@ -85,10 +84,10 @@ class MainActivity : BaseActivity() {
                     R.id.nav_drawer_downloads -> {
                         startActivity(Intent(this, DownloadActivity::class.java))
                     }
-                    R.id.nav_drawer_settings -> {
-                        val intent = Intent(this, SettingsActivity::class.java)
-                        startActivityForResult(intent, REQUEST_OPEN_SETTINGS)
-                    }
+                    R.id.nav_drawer_settings ->
+                        router.pushController(RouterTransaction.with(SettingsMainController())
+                                .pushChangeHandler(FadeChangeHandler())
+                                .popChangeHandler(FadeChangeHandler()))
                 }
             }
             drawer.closeDrawer(GravityCompat.START)
@@ -216,26 +215,7 @@ class MainActivity : BaseActivity() {
         }
     }
 
-    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        if (requestCode == REQUEST_OPEN_SETTINGS && resultCode != 0) {
-            if (resultCode and SettingsActivity.FLAG_DATABASE_CLEARED != 0) {
-                // If database is cleared avoid undefined behavior by recreating the stack.
-                TaskStackBuilder.create(this)
-                        .addNextIntent(Intent(this, MainActivity::class.java))
-                        .startActivities()
-            } else if (resultCode and SettingsActivity.FLAG_THEME_CHANGED != 0) {
-                // Delay activity recreation to avoid fragment leaks.
-                nav_view.post { recreate() }
-            } else if (resultCode and SettingsActivity.FLAG_LANG_CHANGED != 0) {
-                nav_view.post { recreate() }
-            }
-        } else {
-            super.onActivityResult(requestCode, resultCode, data)
-        }
-    }
-
     companion object {
-        private const val REQUEST_OPEN_SETTINGS = 200
         // Shortcut actions
         private const val SHORTCUT_LIBRARY = "eu.kanade.tachiyomi.SHOW_LIBRARY"
         private const val SHORTCUT_RECENTLY_UPDATED = "eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"

+ 1 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt

@@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.download.model.Download
 import eu.kanade.tachiyomi.ui.base.controller.NucleusController
+import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag
 import eu.kanade.tachiyomi.ui.manga.MangaController
 import eu.kanade.tachiyomi.ui.reader.ReaderActivity
 import eu.kanade.tachiyomi.util.getCoordinates

+ 2 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt

@@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.data.download.model.Download
 import eu.kanade.tachiyomi.data.library.LibraryUpdateService
 import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
 import eu.kanade.tachiyomi.ui.base.controller.NucleusController
+import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag
 import eu.kanade.tachiyomi.ui.manga.MangaController
 import eu.kanade.tachiyomi.ui.reader.ReaderActivity
 import eu.kanade.tachiyomi.util.toast
@@ -336,4 +337,4 @@ class RecentChaptersController : NucleusController<RecentChaptersPresenter>(),
         actionMode = null
     }
 
-}
+}

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

@@ -8,6 +8,7 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
 import android.widget.ProgressBar
 import eu.kanade.tachiyomi.data.track.TrackManager
+import eu.kanade.tachiyomi.ui.main.MainActivity
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
 import uy.kohesive.injekt.injectLazy
@@ -41,7 +42,7 @@ class AnilistLoginActivity : AppCompatActivity() {
     private fun returnToSettings() {
         finish()
 
-        val intent = Intent(this, SettingsActivity::class.java)
+        val intent = Intent(this, MainActivity::class.java)
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
         startActivity(intent)
     }

+ 102 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt

@@ -0,0 +1,102 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.content.Context
+import android.support.v4.graphics.drawable.DrawableCompat
+import android.support.v7.preference.*
+import eu.kanade.tachiyomi.widget.preference.IntListPreference
+
+@DslMarker
+@Target(AnnotationTarget.TYPE)
+annotation class DSL
+
+inline fun PreferenceManager.newScreen(context: Context, block: (@DSL PreferenceScreen).() -> Unit): PreferenceScreen {
+    return createPreferenceScreen(context).also { it.block() }
+}
+
+inline fun PreferenceGroup.preference(block: (@DSL Preference).() -> Unit): Preference {
+    return initThenAdd(Preference(context), block)
+}
+
+inline fun PreferenceGroup.switchPreference(block: (@DSL SwitchPreferenceCompat).() -> Unit): SwitchPreferenceCompat {
+    return initThenAdd(SwitchPreferenceCompat(context), block)
+}
+
+inline fun PreferenceGroup.checkBoxPreference(block: (@DSL CheckBoxPreference).() -> Unit): CheckBoxPreference {
+    return initThenAdd(CheckBoxPreference(context), block)
+}
+
+inline fun PreferenceGroup.editTextPreference(block: (@DSL EditTextPreference).() -> Unit): EditTextPreference {
+    return initThenAdd(EditTextPreference(context), block).also(::initDialog)
+}
+
+inline fun PreferenceGroup.listPreference(block: (@DSL ListPreference).() -> Unit): ListPreference {
+    return initThenAdd(ListPreference(context), block).also(::initDialog)
+}
+
+inline fun PreferenceGroup.intListPreference(block: (@DSL IntListPreference).() -> Unit): IntListPreference {
+    return initThenAdd(IntListPreference(context), block).also(::initDialog)
+}
+
+inline fun PreferenceGroup.multiSelectListPreference(block: (@DSL MultiSelectListPreference).() -> Unit): MultiSelectListPreference {
+    return initThenAdd(MultiSelectListPreference(context), block).also(::initDialog)
+}
+
+inline fun PreferenceScreen.preferenceCategory(block: (@DSL PreferenceCategory).() -> Unit): PreferenceCategory {
+    return addThenInit(PreferenceCategory(context), block)
+}
+
+inline fun PreferenceScreen.preferenceScreen(block: (@DSL PreferenceScreen).() -> Unit): PreferenceScreen {
+    return addThenInit(preferenceManager.createPreferenceScreen(context), block)
+}
+
+fun initDialog(dialogPreference: DialogPreference) {
+    with(dialogPreference) {
+        if (dialogTitle == null) {
+            dialogTitle = title
+        }
+    }
+}
+
+inline fun <P : Preference> PreferenceGroup.initThenAdd(p: P, block: P.() -> Unit): P {
+    return p.apply { block(); addPreference(this); }
+}
+
+inline fun <P : Preference> PreferenceGroup.addThenInit(p: P, block: P.() -> Unit): P {
+    return p.apply { addPreference(this); block() }
+}
+
+inline fun Preference.onClick(crossinline block: () -> Unit) {
+    setOnPreferenceClickListener { block(); true }
+}
+
+inline fun Preference.onChange(crossinline block: (Any?) -> Boolean) {
+    setOnPreferenceChangeListener { _, newValue -> block(newValue) }
+}
+
+var Preference.defaultValue: Any?
+    get() = null // set only
+    set(value) { setDefaultValue(value) }
+
+var Preference.titleRes: Int
+    get() = 0 // set only
+    set(value) { setTitle(value) }
+
+var Preference.iconRes: Int
+    get() = 0 // set only
+    set(value) { setIcon(value) }
+
+var Preference.summaryRes: Int
+    get() = 0 // set only
+    set(value) { setSummary(value) }
+
+var Preference.iconTint: Int
+    get() = 0 // set only
+    set(value) { DrawableCompat.setTint(icon, value) }
+
+var ListPreference.entriesRes: Array<Int>
+    get() = emptyArray() // set only
+    set(value) { entries = value.map { context.getString(it) }.toTypedArray() }
+
+var MultiSelectListPreference.entriesRes: Array<Int>
+    get() = emptyArray() // set only
+    set(value) { entries = value.map { context.getString(it) }.toTypedArray() }

+ 166 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt

@@ -0,0 +1,166 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.app.Dialog
+import android.os.Bundle
+import android.support.v7.preference.PreferenceScreen
+import android.view.View
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.kanade.tachiyomi.BuildConfig
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.updater.GithubUpdateChecker
+import eu.kanade.tachiyomi.data.updater.GithubUpdateResult
+import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
+import eu.kanade.tachiyomi.data.updater.UpdateDownloaderService
+import eu.kanade.tachiyomi.ui.base.controller.DialogController
+import eu.kanade.tachiyomi.util.toast
+import rx.Subscription
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
+import timber.log.Timber
+import java.text.DateFormat
+import java.text.ParseException
+import java.text.SimpleDateFormat
+import java.util.*
+import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
+
+class SettingsAboutController : SettingsController() {
+
+    /**
+     * Checks for new releases
+     */
+    private val updateChecker by lazy { GithubUpdateChecker() }
+
+    /**
+     * The subscribtion service of the obtained release object
+     */
+    private var releaseSubscription: Subscription? = null
+
+    private val isUpdaterEnabled = !BuildConfig.DEBUG && BuildConfig.INCLUDE_UPDATER
+
+    override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
+        titleRes = R.string.pref_category_about
+
+        switchPreference {
+            key = "acra.enable"
+            titleRes = R.string.pref_enable_acra
+            summaryRes = R.string.pref_acra_summary
+            defaultValue = true
+        }
+        switchPreference {
+            key = Keys.automaticUpdates
+            titleRes = R.string.pref_enable_automatic_updates
+            summaryRes = R.string.pref_enable_automatic_updates_summary
+            defaultValue = false
+
+            if (isUpdaterEnabled) {
+                onChange { newValue ->
+                    val checked = newValue as Boolean
+                    if (checked) {
+                        UpdateCheckerJob.setupTask()
+                    } else {
+                        UpdateCheckerJob.cancelTask()
+                    }
+                    true
+                }
+            } else {
+                isVisible = false
+            }
+        }
+        preference {
+            titleRes = R.string.version
+            summary = if (BuildConfig.DEBUG)
+                "r" + BuildConfig.COMMIT_COUNT
+            else
+                BuildConfig.VERSION_NAME
+
+            if (isUpdaterEnabled) {
+                onClick { checkVersion() }
+            }
+        }
+        preference {
+            titleRes = R.string.build_time
+            summary = getFormattedBuildTime()
+        }
+    }
+
+    override fun onDestroyView(view: View) {
+        super.onDestroyView(view)
+        releaseSubscription?.unsubscribe()
+        releaseSubscription = null
+    }
+
+    /**
+     * Checks version and shows a user prompt if an update is available.
+     */
+    private fun checkVersion() {
+        if (activity == null) return
+
+        activity?.toast(R.string.update_check_look_for_updates)
+        releaseSubscription?.unsubscribe()
+        releaseSubscription = updateChecker.checkForUpdate()
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({ result ->
+                    when (result) {
+                        is GithubUpdateResult.NewUpdate -> {
+                            val body = result.release.changeLog
+                            val url = result.release.downloadLink
+
+                            // Create confirmation window
+                            NewUpdateDialogController(body, url).showDialog(router)
+                        }
+                        is GithubUpdateResult.NoNewUpdate -> {
+                            activity?.toast(R.string.update_check_no_new_updates)
+                        }
+                    }
+                }, { error ->
+                    Timber.e(error)
+                })
+    }
+
+    class NewUpdateDialogController(bundle: Bundle? = null) : DialogController(bundle) {
+
+        constructor(body: String, url: String) : this(Bundle().apply {
+            putString(BODY_KEY, body)
+            putString(URL_KEY, url)
+        })
+
+        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+            return MaterialDialog.Builder(activity!!)
+                    .title(R.string.update_check_title)
+                    .content(args.getString(BODY_KEY))
+                    .positiveText(R.string.update_check_confirm)
+                    .negativeText(R.string.update_check_ignore)
+                    .onPositive { _, _ ->
+                        val appContext = applicationContext
+                        if (appContext != null) {
+                            // Start download
+                            val url = args.getString(URL_KEY)
+                            UpdateDownloaderService.downloadUpdate(appContext, url)
+                        }
+                    }
+                    .build()
+        }
+
+        private companion object {
+            const val BODY_KEY = "NewUpdateDialogController.body"
+            const val URL_KEY = "NewUpdateDialogController.key"
+        }
+    }
+
+    private fun getFormattedBuildTime(): String {
+        try {
+            val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US)
+            inputDf.timeZone = TimeZone.getTimeZone("UTC")
+            val date = inputDf.parse(BuildConfig.BUILD_TIME)
+
+            val outputDf = DateFormat.getDateTimeInstance(
+                    DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault())
+            outputDf.timeZone = TimeZone.getDefault()
+
+            return outputDf.format(date)
+        } catch (e: ParseException) {
+            return BuildConfig.BUILD_TIME
+        }
+    }
+}

+ 0 - 139
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt

@@ -1,139 +0,0 @@
-package eu.kanade.tachiyomi.ui.setting
-
-import android.os.Bundle
-import android.support.v7.preference.XpPreferenceFragment
-import android.view.View
-import com.afollestad.materialdialogs.MaterialDialog
-import eu.kanade.tachiyomi.BuildConfig
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.updater.GithubUpdateChecker
-import eu.kanade.tachiyomi.data.updater.GithubUpdateResult
-import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
-import eu.kanade.tachiyomi.data.updater.UpdateDownloaderService
-import eu.kanade.tachiyomi.util.toast
-import net.xpece.android.support.preference.SwitchPreference
-import rx.Subscription
-import rx.android.schedulers.AndroidSchedulers
-import rx.schedulers.Schedulers
-import timber.log.Timber
-import java.text.DateFormat
-import java.text.ParseException
-import java.text.SimpleDateFormat
-import java.util.*
-
-class SettingsAboutFragment : SettingsFragment() {
-
-    companion object {
-        fun newInstance(rootKey: String): SettingsAboutFragment {
-            val args = Bundle()
-            args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
-            return SettingsAboutFragment().apply { arguments = args }
-        }
-    }
-
-    /**
-     * Checks for new releases
-     */
-    private val updateChecker by lazy { GithubUpdateChecker() }
-
-    /**
-     * The subscribtion service of the obtained release object
-     */
-    private var releaseSubscription: Subscription? = null
-
-    val automaticUpdates: SwitchPreference by bindPref(R.string.pref_enable_automatic_updates_key)
-
-    override fun onViewCreated(view: View, savedState: Bundle?) {
-        super.onViewCreated(view, savedState)
-
-        val version = findPreference(getString(R.string.pref_version))
-        val buildTime = findPreference(getString(R.string.pref_build_time))
-
-        version.summary = if (BuildConfig.DEBUG)
-            "r" + BuildConfig.COMMIT_COUNT
-        else
-            BuildConfig.VERSION_NAME
-
-        if (!BuildConfig.DEBUG && BuildConfig.INCLUDE_UPDATER) {
-            //Set onClickListener to check for new version
-            version.setOnPreferenceClickListener {
-                checkVersion()
-                true
-            }
-
-            automaticUpdates.setOnPreferenceChangeListener { preference, any ->
-                val checked = any as Boolean
-                if (checked) {
-                    UpdateCheckerJob.setupTask()
-                } else {
-                    UpdateCheckerJob.cancelTask()
-                }
-                true
-            }
-        } else {
-            automaticUpdates.isVisible = false
-        }
-
-        buildTime.summary = getFormattedBuildTime()
-    }
-
-    override fun onDestroyView() {
-        releaseSubscription?.unsubscribe()
-        super.onDestroyView()
-    }
-
-    private fun getFormattedBuildTime(): String {
-        try {
-            val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'")
-            inputDf.timeZone = TimeZone.getTimeZone("UTC")
-            val date = inputDf.parse(BuildConfig.BUILD_TIME)
-
-            val outputDf = DateFormat.getDateTimeInstance(
-                    DateFormat.MEDIUM, DateFormat.SHORT, Locale.getDefault())
-            outputDf.timeZone = TimeZone.getDefault()
-
-            return outputDf.format(date)
-        } catch (e: ParseException) {
-            return BuildConfig.BUILD_TIME
-        }
-    }
-
-    /**
-     * Checks version and shows a user prompt if an update is available.
-     */
-    private fun checkVersion() {
-        releaseSubscription?.unsubscribe()
-
-        context.toast(R.string.update_check_look_for_updates)
-
-        releaseSubscription = updateChecker.checkForUpdate()
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe({ result ->
-                    when (result) {
-                        is GithubUpdateResult.NewUpdate -> {
-                            val body = result.release.changeLog
-                            val url = result.release.downloadLink
-
-                            // Create confirmation window
-                            MaterialDialog.Builder(context)
-                                    .title(R.string.update_check_title)
-                                    .content(body)
-                                    .positiveText(getString(R.string.update_check_confirm))
-                                    .negativeText(getString(R.string.update_check_ignore))
-                                    .onPositive { dialog, which ->
-                                        // Start download
-                                        UpdateDownloaderService.downloadUpdate(context, url)
-                                    }
-                                    .show()
-                        }
-                        is GithubUpdateResult.NoNewUpdate -> {
-                            context.toast(R.string.update_check_no_new_updates)
-                        }
-                    }
-                }, { error ->
-                    Timber.e(error)
-                })
-    }
-
-}

+ 0 - 86
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt

@@ -1,86 +0,0 @@
-package eu.kanade.tachiyomi.ui.setting
-
-import android.os.Bundle
-import android.support.v7.preference.PreferenceFragmentCompat
-import android.support.v7.preference.PreferenceScreen
-import android.view.MenuItem
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
-import kotlinx.android.synthetic.main.toolbar.*
-import net.xpece.android.support.preference.PreferenceScreenNavigationStrategy
-import net.xpece.android.support.preference.PreferenceScreenNavigationStrategy.ReplaceFragment
-
-class SettingsActivity : BaseActivity(),
-        PreferenceFragmentCompat.OnPreferenceStartScreenCallback,
-        PreferenceScreenNavigationStrategy.ReplaceFragment.Callbacks {
-
-    private lateinit var replaceFragmentStrategy: ReplaceFragment
-
-    /**
-     * Flags to send to the parent activity for reacting to preference changes.
-     */
-    var parentFlags = 0
-        set(value) {
-            field = field or value
-            setResult(field)
-        }
-
-    override fun onCreate(savedState: Bundle?) {
-        setAppTheme()
-        super.onCreate(savedState)
-        setTitle(R.string.label_settings)
-        setContentView(R.layout.activity_preferences)
-
-        replaceFragmentStrategy = ReplaceFragment(this,
-                R.anim.abc_fade_in, R.anim.abc_fade_out,
-                R.anim.abc_fade_in, R.anim.abc_fade_out)
-
-        if (savedState == null) {
-            supportFragmentManager.beginTransaction()
-                .add(R.id.settings_content, SettingsFragment.newInstance(null), "Settings")
-                .commit()
-        } else {
-            parentFlags = savedState.getInt(SettingsActivity::parentFlags.name)
-        }
-
-        setupToolbar(toolbar, backNavigation = false)
-    }
-
-    override fun onSaveInstanceState(outState: Bundle) {
-        outState.putInt(SettingsActivity::parentFlags.name, parentFlags)
-        super.onSaveInstanceState(outState)
-    }
-
-    override fun onOptionsItemSelected(item: MenuItem): Boolean {
-        when (item.itemId) {
-            android.R.id.home -> onBackPressed()
-            else -> return super.onOptionsItemSelected(item)
-        }
-        return true
-    }
-
-    override fun onBuildPreferenceFragment(key: String?): PreferenceFragmentCompat {
-        return when (key) {
-            "general_screen" -> SettingsGeneralFragment.newInstance(key)
-            "downloads_screen" -> SettingsDownloadsFragment.newInstance(key)
-            "sources_screen" -> SettingsSourcesFragment.newInstance(key)
-            "tracking_screen" -> SettingsTrackingFragment.newInstance(key)
-            "backup_screen" -> SettingsBackupFragment.newInstance(key)
-            "advanced_screen" -> SettingsAdvancedFragment.newInstance(key)
-            "about_screen" -> SettingsAboutFragment.newInstance(key)
-            else -> SettingsFragment.newInstance(key)
-        }
-    }
-
-    override fun onPreferenceStartScreen(p0: PreferenceFragmentCompat, p1: PreferenceScreen): Boolean {
-        replaceFragmentStrategy.onPreferenceStartScreen(supportFragmentManager, p0, p1)
-        return true
-    }
-
-    companion object {
-        const val FLAG_THEME_CHANGED = 0x1
-        const val FLAG_DATABASE_CLEARED = 0x2
-        const val FLAG_LANG_CHANGED = 0x4
-    }
-
-}

+ 159 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt

@@ -0,0 +1,159 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.app.Dialog
+import android.os.Bundle
+import android.support.v7.preference.PreferenceScreen
+import android.view.View
+import com.afollestad.materialdialogs.MaterialDialog
+import com.bluelinelabs.conductor.RouterTransaction
+import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.cache.ChapterCache
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.library.LibraryUpdateService
+import eu.kanade.tachiyomi.network.NetworkHelper
+import eu.kanade.tachiyomi.ui.base.controller.DialogController
+import eu.kanade.tachiyomi.ui.library.LibraryController
+import eu.kanade.tachiyomi.util.toast
+import rx.Observable
+import rx.android.schedulers.AndroidSchedulers
+import rx.schedulers.Schedulers
+import uy.kohesive.injekt.injectLazy
+
+class SettingsAdvancedController : SettingsController() {
+
+    private val network: NetworkHelper by injectLazy()
+
+    private val chapterCache: ChapterCache by injectLazy()
+
+    private val db: DatabaseHelper by injectLazy()
+
+    override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
+        titleRes = R.string.pref_category_advanced
+
+        preference {
+            key = CLEAR_CACHE_KEY
+            titleRes = R.string.pref_clear_chapter_cache
+            summary = context.getString(R.string.used_cache, chapterCache.readableSize)
+
+            onClick { clearChapterCache() }
+        }
+        preference {
+            titleRes = R.string.pref_clear_cookies
+
+            onClick {
+                network.cookies.removeAll()
+                activity?.toast(R.string.cookies_cleared)
+            }
+        }
+        preference {
+            titleRes = R.string.pref_clear_database
+            summaryRes = R.string.pref_clear_database_summary
+
+            onClick {
+                val ctrl = ClearDatabaseDialogController()
+                ctrl.targetController = this@SettingsAdvancedController
+                ctrl.showDialog(router)
+            }
+        }
+        preference {
+            titleRes = R.string.pref_refresh_library_metadata
+            summaryRes = R.string.pref_refresh_library_metadata_summary
+
+            onClick { LibraryUpdateService.start(context, details = true) }
+        }
+    }
+
+    private fun clearChapterCache() {
+        if (activity == null) return
+        val files = chapterCache.cacheDir.listFiles() ?: return
+
+        var deletedFiles = 0
+
+        val ctrl = DeletingFilesDialogController()
+        ctrl.total = files.size
+        ctrl.showDialog(router)
+
+        Observable.defer { Observable.from(files) }
+                .doOnNext { file ->
+                    if (chapterCache.removeFileFromCache(file.name)) {
+                        deletedFiles++
+                    }
+                }
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({
+                    ctrl.setProgress(deletedFiles)
+                }, {
+                    activity?.toast(R.string.cache_delete_error)
+                }, {
+                    ctrl.finish()
+                    activity?.toast(resources?.getString(R.string.cache_deleted, deletedFiles))
+                    findPreference(CLEAR_CACHE_KEY)?.summary =
+                            resources?.getString(R.string.used_cache, chapterCache.readableSize)
+                })
+    }
+
+    class DeletingFilesDialogController : DialogController() {
+
+        var total = 0
+
+        private var materialDialog: MaterialDialog? = null
+
+        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+            return MaterialDialog.Builder(activity!!)
+                    .title(R.string.deleting)
+                    .progress(false, total, true)
+                    .cancelable(false)
+                    .build()
+                    .also { materialDialog = it }
+        }
+
+        override fun onDestroyView(view: View) {
+            super.onDestroyView(view)
+            materialDialog = null
+        }
+
+        override fun onRestoreInstanceState(savedInstanceState: Bundle) {
+            super.onRestoreInstanceState(savedInstanceState)
+            finish()
+        }
+
+        fun setProgress(deletedFiles: Int) {
+            materialDialog?.setProgress(deletedFiles)
+        }
+
+        fun finish() {
+            router.popController(this)
+        }
+    }
+
+    class ClearDatabaseDialogController : DialogController() {
+        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+            return MaterialDialog.Builder(activity!!)
+                    .content(R.string.clear_database_confirmation)
+                    .positiveText(android.R.string.yes)
+                    .negativeText(android.R.string.no)
+                    .onPositive { _, _ ->
+                        (targetController as? SettingsAdvancedController)?.clearDatabase()
+                    }
+                    .build()
+        }
+    }
+
+    private fun clearDatabase() {
+        // Avoid weird behavior by going back to the library.
+        val newBackstack = listOf(RouterTransaction.with(LibraryController())) +
+                router.backstack.drop(1)
+
+        router.setBackstack(newBackstack, FadeChangeHandler())
+
+        db.deleteMangasNotInLibrary().executeAsBlocking()
+        db.deleteHistoryNoLastRead().executeAsBlocking()
+        activity?.toast(R.string.clear_database_completed)
+    }
+
+    private companion object {
+        const val CLEAR_CACHE_KEY = "pref_clear_cache_key"
+    }
+}

+ 0 - 117
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt

@@ -1,117 +0,0 @@
-package eu.kanade.tachiyomi.ui.setting
-
-import android.os.Bundle
-import android.support.v7.preference.Preference
-import android.support.v7.preference.XpPreferenceFragment
-import android.view.View
-import com.afollestad.materialdialogs.MaterialDialog
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.cache.ChapterCache
-import eu.kanade.tachiyomi.data.database.DatabaseHelper
-import eu.kanade.tachiyomi.data.library.LibraryUpdateService
-import eu.kanade.tachiyomi.network.NetworkHelper
-import eu.kanade.tachiyomi.util.plusAssign
-import eu.kanade.tachiyomi.util.toast
-import rx.Observable
-import rx.android.schedulers.AndroidSchedulers
-import rx.schedulers.Schedulers
-import uy.kohesive.injekt.injectLazy
-import java.util.concurrent.atomic.AtomicInteger
-
-class SettingsAdvancedFragment : SettingsFragment() {
-
-    companion object {
-        fun newInstance(rootKey: String): SettingsAdvancedFragment {
-            val args = Bundle()
-            args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
-            return SettingsAdvancedFragment().apply { arguments = args }
-        }
-    }
-
-    private val network: NetworkHelper by injectLazy()
-
-    private val chapterCache: ChapterCache by injectLazy()
-
-    private val db: DatabaseHelper by injectLazy()
-
-    private val clearCache: Preference by bindPref(R.string.pref_clear_chapter_cache_key)
-
-    private val clearDatabase: Preference by bindPref(R.string.pref_clear_database_key)
-
-    private val clearCookies: Preference by bindPref(R.string.pref_clear_cookies_key)
-
-    private val refreshMetadata: Preference by bindPref(R.string.pref_refresh_library_metadata_key)
-
-    override fun onViewCreated(view: View, savedState: Bundle?) {
-        super.onViewCreated(view, savedState)
-
-        clearCache.setOnPreferenceClickListener {
-            clearChapterCache()
-            true
-        }
-        clearCache.summary = getString(R.string.used_cache, chapterCache.readableSize)
-
-        clearCookies.setOnPreferenceClickListener {
-            network.cookies.removeAll()
-            activity.toast(R.string.cookies_cleared)
-            true
-        }
-
-        clearDatabase.setOnPreferenceClickListener {
-            clearDatabase()
-            true
-        }
-
-        refreshMetadata.setOnPreferenceClickListener {
-            LibraryUpdateService.start(context, details = true)
-            true
-        }
-    }
-
-    private fun clearChapterCache() {
-        val deletedFiles = AtomicInteger()
-
-        val files = chapterCache.cacheDir.listFiles() ?: return
-
-        val dialog = MaterialDialog.Builder(activity)
-                .title(R.string.deleting)
-                .progress(false, files.size, true)
-                .cancelable(false)
-                .show()
-
-        subscriptions += Observable.defer { Observable.from(files) }
-                .concatMap { file ->
-                    if (chapterCache.removeFileFromCache(file.name)) {
-                        deletedFiles.incrementAndGet()
-                    }
-                    Observable.just(file)
-                }
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe({
-                    dialog.incrementProgress(1)
-                }, {
-                    dialog.dismiss()
-                    activity.toast(R.string.cache_delete_error)
-                }, {
-                    dialog.dismiss()
-                    activity.toast(getString(R.string.cache_deleted, deletedFiles.get()))
-                    clearCache.summary = getString(R.string.used_cache, chapterCache.readableSize)
-                })
-    }
-
-    private fun clearDatabase() {
-        MaterialDialog.Builder(activity)
-                .content(R.string.clear_database_confirmation)
-                .positiveText(android.R.string.yes)
-                .negativeText(android.R.string.no)
-                .onPositive { dialog, which ->
-                    (activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_DATABASE_CLEARED
-                    db.deleteMangasNotInLibrary().executeAsBlocking()
-                    db.deleteHistoryNoLastRead().executeAsBlocking()
-                    activity.toast(R.string.clear_database_completed)
-                }
-                .show()
-    }
-
-}

+ 458 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt

@@ -0,0 +1,458 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.Manifest.permission.READ_EXTERNAL_STORAGE
+import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+import android.app.Activity
+import android.app.Dialog
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.support.v7.preference.PreferenceScreen
+import android.view.View
+import com.afollestad.materialdialogs.MaterialDialog
+import com.hippo.unifile.UniFile
+import com.nononsenseapps.filepicker.FilePickerActivity
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.backup.BackupConst
+import eu.kanade.tachiyomi.data.backup.BackupCreateService
+import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
+import eu.kanade.tachiyomi.data.backup.BackupRestoreService
+import eu.kanade.tachiyomi.data.backup.models.Backup
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.ui.base.controller.DialogController
+import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag
+import eu.kanade.tachiyomi.util.getUriCompat
+import eu.kanade.tachiyomi.util.registerLocalReceiver
+import eu.kanade.tachiyomi.util.toast
+import eu.kanade.tachiyomi.util.unregisterLocalReceiver
+import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import java.io.File
+import java.util.concurrent.TimeUnit
+import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
+
+class SettingsBackupController : SettingsController() {
+
+    /**
+     * Flags containing information of what to backup.
+     */
+    private var backupFlags = 0
+
+    private val receiver = BackupBroadcastReceiver()
+
+    init {
+        preferences.context.registerLocalReceiver(receiver, IntentFilter(BackupConst.INTENT_FILTER))
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            requestPermissions(arrayOf(WRITE_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE), 500)
+        }
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        preferences.context.unregisterLocalReceiver(receiver)
+    }
+
+    override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
+        titleRes = R.string.backup
+
+        preference {
+            titleRes = R.string.pref_create_backup
+            summaryRes = R.string.pref_create_backup_summ
+
+            onClick {
+                val ctrl = CreateBackupDialog()
+                ctrl.targetController = this@SettingsBackupController
+                ctrl.showDialog(router)
+            }
+        }
+        preference {
+            titleRes = R.string.pref_restore_backup
+            summaryRes = R.string.pref_restore_backup_summ
+
+            onClick {
+                val intent = Intent(Intent.ACTION_GET_CONTENT)
+                intent.addCategory(Intent.CATEGORY_OPENABLE)
+                intent.type = "application/*"
+                val title = resources?.getString(R.string.file_select_backup)
+                val chooser = Intent.createChooser(intent, title)
+                startActivityForResult(chooser, CODE_BACKUP_RESTORE)
+            }
+        }
+        preferenceCategory {
+            titleRes = R.string.pref_backup_service_category
+
+            intListPreference {
+                key = Keys.backupInterval
+                titleRes = R.string.pref_backup_interval
+                entriesRes = arrayOf(R.string.update_never, R.string.update_6hour,
+                        R.string.update_12hour, R.string.update_24hour,
+                        R.string.update_48hour, R.string.update_weekly)
+                entryValues = arrayOf("0", "6", "12", "24", "168")
+                defaultValue = "0"
+                summary = "%s"
+
+                onChange { newValue ->
+                    // Always cancel the previous task, it seems that sometimes they are not updated
+                    BackupCreatorJob.cancelTask()
+
+                    val interval = (newValue as String).toInt()
+                    if (interval > 0) {
+                        BackupCreatorJob.setupTask(interval)
+                    }
+                    true
+                }
+            }
+            val backupDir = preference {
+                key = Keys.backupDirectory
+                titleRes = R.string.pref_backup_directory
+
+                onClick {
+                    val currentDir = preferences.backupsDirectory().getOrDefault()
+
+                    val intent = if (Build.VERSION.SDK_INT < 21) {
+                        // Custom dir selected, open directory selector
+                        val i = Intent(activity, CustomLayoutPickerActivity::class.java)
+                        i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
+                        i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
+                        i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
+                        i.putExtra(FilePickerActivity.EXTRA_START_PATH, currentDir)
+
+                    } else {
+                        Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+                    }
+                    startActivityForResult(intent, CODE_BACKUP_DIR)
+                }
+
+                preferences.backupsDirectory().asObservable()
+                        .subscribeUntilDestroy { path ->
+                            val dir = UniFile.fromUri(context, Uri.parse(path))
+                            summary = dir.filePath ?: path
+                        }
+            }
+            val backupNumber = intListPreference {
+                key = Keys.numberOfBackups
+                titleRes = R.string.pref_backup_slots
+                entries = arrayOf("1", "2", "3", "4", "5")
+                entryValues = entries
+                defaultValue = "1"
+                summary = "%s"
+            }
+
+            preferences.backupInterval().asObservable()
+                    .subscribeUntilDestroy {
+                        backupDir.isVisible = it > 0
+                        backupNumber.isVisible = it > 0
+                    }
+        }
+
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        when (requestCode) {
+            CODE_BACKUP_DIR -> if (data != null && resultCode == Activity.RESULT_OK) {
+                val activity = activity ?: return
+                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+                    val uri = Uri.fromFile(File(data.data.path))
+                    preferences.backupsDirectory().set(uri.toString())
+                } else {
+                    val uri = data.data
+                    val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
+                            Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+
+                    activity.contentResolver.takePersistableUriPermission(uri, flags)
+
+                    val file = UniFile.fromUri(activity, uri)
+                    preferences.backupsDirectory().set(file.uri.toString())
+                }
+            }
+            CODE_BACKUP_CREATE -> if (data != null && resultCode == Activity.RESULT_OK) {
+                val activity = activity ?: return
+                val path = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+                    val dir = data.data.path
+                    val file = File(dir, Backup.getDefaultFilename())
+
+                    file.absolutePath
+                } else {
+                    val uri = data.data
+                    val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
+                            Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+
+                    activity.contentResolver.takePersistableUriPermission(uri, flags)
+                    val file = UniFile.fromUri(activity, uri)
+
+                    file.uri.toString()
+                }
+
+                CreatingBackupDialog().showDialog(router, TAG_CREATING_BACKUP_DIALOG)
+                BackupCreateService.makeBackup(activity, path, backupFlags)
+            }
+            CODE_BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) {
+                val uri = data.data
+                RestoreBackupDialog(uri).showDialog(router)
+            }
+        }
+    }
+
+    fun createBackup(flags: Int) {
+        backupFlags = flags
+
+        // If API lower as KitKat use custom dir picker
+        val intent = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+            // Get dirs
+            val preferences: PreferencesHelper = Injekt.get()
+            val currentDir = preferences.backupsDirectory().getOrDefault()
+
+            Intent(activity, CustomLayoutPickerActivity::class.java)
+                    .putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
+                    .putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
+                    .putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
+                    .putExtra(FilePickerActivity.EXTRA_START_PATH, currentDir)
+        } else {
+            // Use Androids build in file creator
+            Intent(Intent.ACTION_CREATE_DOCUMENT)
+                    .addCategory(Intent.CATEGORY_OPENABLE)
+                    .setType("application/*")
+                    .putExtra(Intent.EXTRA_TITLE, Backup.getDefaultFilename())
+        }
+        startActivityForResult(intent, CODE_BACKUP_CREATE)
+    }
+
+    class CreateBackupDialog : DialogController() {
+        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+            return MaterialDialog.Builder(activity!!)
+                    .title(R.string.pref_create_backup)
+                    .content(R.string.backup_choice)
+                    .items(R.array.backup_options)
+                    .itemsDisabledIndices(0)
+                    .itemsCallbackMultiChoice(arrayOf(0, 1, 2, 3, 4), { _, positions, _ ->
+                        var flags = 0
+                        for (i in 1..positions.size - 1) {
+                            when (positions[i]) {
+                                1 -> flags = flags or BackupCreateService.BACKUP_CATEGORY
+                                2 -> flags = flags or BackupCreateService.BACKUP_CHAPTER
+                                3 -> flags = flags or BackupCreateService.BACKUP_TRACK
+                                4 -> flags = flags or BackupCreateService.BACKUP_HISTORY
+                            }
+                        }
+
+                        (targetController as? SettingsBackupController)?.createBackup(flags)
+                        true
+                    })
+                    .positiveText(R.string.action_create)
+                    .negativeText(android.R.string.cancel)
+                    .build()
+        }
+    }
+
+    class CreatingBackupDialog : DialogController() {
+        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+            return MaterialDialog.Builder(activity!!)
+                    .title(R.string.backup)
+                    .content(R.string.creating_backup)
+                    .progress(true, 0)
+                    .cancelable(false)
+                    .build()
+        }
+
+        override fun onRestoreInstanceState(savedInstanceState: Bundle) {
+            super.onRestoreInstanceState(savedInstanceState)
+            router.popController(this)
+        }
+    }
+
+    class CreatedBackupDialog(bundle: Bundle? = null) : DialogController(bundle) {
+        constructor(uri: Uri) : this(Bundle().apply {
+            putParcelable(KEY_URI, uri)
+        })
+
+        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+            val activity = activity!!
+            val unifile = UniFile.fromUri(activity, args.getParcelable<Uri>(KEY_URI))
+            return MaterialDialog.Builder(activity)
+                    .title(R.string.backup_created)
+                    .content(activity.getString(R.string.file_saved, unifile.filePath))
+                    .positiveText(R.string.action_close)
+                    .negativeText(R.string.action_export)
+                    .onNegative { _, _ ->
+                        val sendIntent = Intent(Intent.ACTION_SEND)
+                        sendIntent.type = "application/json"
+                        sendIntent.putExtra(Intent.EXTRA_STREAM, unifile.uri)
+                        startActivity(Intent.createChooser(sendIntent, ""))
+                    }
+                    .build()
+        }
+
+        private companion object {
+            const val KEY_URI = "BackupCreatedDialog.uri"
+        }
+    }
+
+    class RestoreBackupDialog(bundle: Bundle? = null) : DialogController(bundle) {
+        constructor(uri: Uri) : this(Bundle().apply {
+            putParcelable(KEY_URI, uri)
+        })
+
+        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+            return MaterialDialog.Builder(activity!!)
+                    .title(R.string.pref_restore_backup)
+                    .content(R.string.backup_restore_content)
+                    .positiveText(R.string.action_restore)
+                    .onPositive { _, _ ->
+                        val context = applicationContext
+                        if (context != null) {
+                            RestoringBackupDialog().showDialog(router, TAG_RESTORING_BACKUP_DIALOG)
+                            BackupRestoreService.start(context, args.getParcelable<Uri>(KEY_URI))
+                        }
+                    }
+                    .build()
+        }
+
+        private companion object {
+            const val KEY_URI = "RestoreBackupDialog.uri"
+        }
+    }
+
+    class RestoringBackupDialog : DialogController() {
+        private var materialDialog: MaterialDialog? = null
+
+        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+            return MaterialDialog.Builder(activity!!)
+                    .title(R.string.backup)
+                    .content(R.string.restoring_backup)
+                    .progress(false, 100, true)
+                    .cancelable(false)
+                    .negativeText(R.string.action_stop)
+                    .onNegative { _, _ ->
+                        applicationContext?.let { BackupRestoreService.stop(it) }
+                    }
+                    .build()
+                    .also { materialDialog = it }
+        }
+
+        override fun onDestroyView(view: View) {
+            super.onDestroyView(view)
+            materialDialog = null
+        }
+
+        override fun onRestoreInstanceState(savedInstanceState: Bundle) {
+            super.onRestoreInstanceState(savedInstanceState)
+            router.popController(this)
+        }
+
+        fun updateProgress(content: String?, progress: Int, amount: Int) {
+            val dialog = materialDialog ?: return
+            dialog.setContent(content)
+            dialog.setProgress(progress)
+            dialog.maxProgress = amount
+        }
+    }
+
+    class RestoredBackupDialog(bundle: Bundle? = null) : DialogController(bundle) {
+        constructor(time: Long, errorCount: Int, path: String, file: String) : this(Bundle().apply {
+            putLong(KEY_TIME, time)
+            putInt(KEY_ERROR_COUNT, errorCount)
+            putString(KEY_PATH, path)
+            putString(KEY_FILE, file)
+        })
+
+        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+            val activity = activity!!
+            val time = args.getLong(KEY_TIME)
+            val errors = args.getInt(KEY_ERROR_COUNT)
+            val path = args.getString(KEY_PATH)
+            val file = args.getString(KEY_FILE)
+            val timeString = String.format("%02d min, %02d sec",
+                    TimeUnit.MILLISECONDS.toMinutes(time),
+                    TimeUnit.MILLISECONDS.toSeconds(time) - TimeUnit.MINUTES.toSeconds(
+                            TimeUnit.MILLISECONDS.toMinutes(time))
+            )
+
+            return MaterialDialog.Builder(activity)
+                    .title(R.string.restore_completed)
+                    .content(activity.getString(R.string.restore_completed_content, timeString,
+                            if (errors > 0) "$errors" else activity.getString(android.R.string.no)))
+                    .positiveText(R.string.action_close)
+                    .negativeText(R.string.action_open_log)
+                    .onNegative { _, _ ->
+                        val context = applicationContext ?: return@onNegative
+                        if (!path.isEmpty()) {
+                            val destFile = File(path, file)
+                            val uri = destFile.getUriCompat(context)
+                            val sendIntent = Intent(Intent.ACTION_VIEW).apply {
+                                setDataAndType(uri, "text/plain")
+                                flags = Intent.FLAG_ACTIVITY_NEW_TASK or
+                                        Intent.FLAG_GRANT_READ_URI_PERMISSION
+                            }
+                            startActivity(sendIntent)
+                        } else {
+                            context.toast(context.getString(R.string.error_opening_log))
+                        }
+                    }
+                    .build()
+        }
+
+        private companion object {
+            const val KEY_TIME = "RestoredBackupDialog.time"
+            const val KEY_ERROR_COUNT = "RestoredBackupDialog.errors"
+            const val KEY_PATH = "RestoredBackupDialog.path"
+            const val KEY_FILE = "RestoredBackupDialog.file"
+        }
+    }
+
+    inner class BackupBroadcastReceiver : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            when (intent.getStringExtra(BackupConst.ACTION)) {
+                BackupConst.ACTION_BACKUP_COMPLETED_DIALOG -> {
+                    router.popControllerWithTag(TAG_CREATING_BACKUP_DIALOG)
+                    val uri = Uri.parse(intent.getStringExtra(BackupConst.EXTRA_URI))
+                    CreatedBackupDialog(uri).showDialog(router)
+                }
+                BackupConst.ACTION_SET_PROGRESS_DIALOG -> {
+                    val progress = intent.getIntExtra(BackupConst.EXTRA_PROGRESS, 0)
+                    val amount = intent.getIntExtra(BackupConst.EXTRA_AMOUNT, 0)
+                    val content = intent.getStringExtra(BackupConst.EXTRA_CONTENT)
+                    (router.getControllerWithTag(TAG_RESTORING_BACKUP_DIALOG)
+                            as? RestoringBackupDialog)?.updateProgress(content, progress, amount)
+                }
+                BackupConst.ACTION_RESTORE_COMPLETED_DIALOG -> {
+                    router.popControllerWithTag(TAG_RESTORING_BACKUP_DIALOG)
+                    val time = intent.getLongExtra(BackupConst.EXTRA_TIME, 0)
+                    val errors = intent.getIntExtra(BackupConst.EXTRA_ERRORS, 0)
+                    val path = intent.getStringExtra(BackupConst.EXTRA_ERROR_FILE_PATH)
+                    val file = intent.getStringExtra(BackupConst.EXTRA_ERROR_FILE)
+                    if (errors > 0) {
+                        RestoredBackupDialog(time, errors, path, file).showDialog(router)
+                    }
+                }
+                BackupConst.ACTION_ERROR_BACKUP_DIALOG -> {
+                    router.popControllerWithTag(TAG_CREATING_BACKUP_DIALOG)
+                    context.toast(intent.getStringExtra(BackupConst.EXTRA_ERROR_MESSAGE))
+                }
+                BackupConst.ACTION_ERROR_RESTORE_DIALOG -> {
+                    router.popControllerWithTag(TAG_RESTORING_BACKUP_DIALOG)
+                    context.toast(intent.getStringExtra(BackupConst.EXTRA_ERROR_MESSAGE))
+                }
+            }
+        }
+    }
+
+    private companion object {
+        const val CODE_BACKUP_CREATE = 501
+        const val CODE_BACKUP_RESTORE = 502
+        const val CODE_BACKUP_DIR = 503
+
+        const val TAG_CREATING_BACKUP_DIALOG = "CreatingBackupDialog"
+        const val TAG_RESTORING_BACKUP_DIALOG = "RestoringBackupDialog"
+    }
+
+}

+ 0 - 407
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupFragment.kt

@@ -1,407 +0,0 @@
-package eu.kanade.tachiyomi.ui.setting
-
-import android.app.Activity
-import android.app.Dialog
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.net.Uri
-import android.os.Build
-import android.os.Bundle
-import android.support.v7.preference.XpPreferenceFragment
-import android.view.View
-import com.afollestad.materialdialogs.MaterialDialog
-import com.hippo.unifile.UniFile
-import com.nononsenseapps.filepicker.FilePickerActivity
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.backup.BackupCreateService
-import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
-import eu.kanade.tachiyomi.data.backup.BackupRestoreService
-import eu.kanade.tachiyomi.data.backup.models.Backup
-import eu.kanade.tachiyomi.data.preference.PreferencesHelper
-import eu.kanade.tachiyomi.data.preference.getOrDefault
-import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
-import eu.kanade.tachiyomi.util.*
-import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
-import eu.kanade.tachiyomi.widget.preference.IntListPreference
-import net.xpece.android.support.preference.Preference
-import rx.subscriptions.Subscriptions
-import uy.kohesive.injekt.injectLazy
-import java.io.File
-import java.util.concurrent.TimeUnit
-import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
-
-/**
- * Settings for [BackupCreateService] and [BackupRestoreService]
- */
-class SettingsBackupFragment : SettingsFragment() {
-
-    companion object {
-        const val INTENT_FILTER = "SettingsBackupFragment"
-        const val ACTION_BACKUP_COMPLETED_DIALOG = "$ID.$INTENT_FILTER.ACTION_BACKUP_COMPLETED_DIALOG"
-        const val ACTION_SET_PROGRESS_DIALOG = "$ID.$INTENT_FILTER.ACTION_SET_PROGRESS_DIALOG"
-        const val ACTION_ERROR_BACKUP_DIALOG = "$ID.$INTENT_FILTER.ACTION_ERROR_BACKUP_DIALOG"
-        const val ACTION_ERROR_RESTORE_DIALOG = "$ID.$INTENT_FILTER.ACTION_ERROR_RESTORE_DIALOG"
-        const val ACTION_RESTORE_COMPLETED_DIALOG = "$ID.$INTENT_FILTER.ACTION_RESTORE_COMPLETED_DIALOG"
-        const val ACTION = "$ID.$INTENT_FILTER.ACTION"
-        const val EXTRA_PROGRESS = "$ID.$INTENT_FILTER.EXTRA_PROGRESS"
-        const val EXTRA_AMOUNT = "$ID.$INTENT_FILTER.EXTRA_AMOUNT"
-        const val EXTRA_ERRORS = "$ID.$INTENT_FILTER.EXTRA_ERRORS"
-        const val EXTRA_CONTENT = "$ID.$INTENT_FILTER.EXTRA_CONTENT"
-        const val EXTRA_ERROR_MESSAGE = "$ID.$INTENT_FILTER.EXTRA_ERROR_MESSAGE"
-        const val EXTRA_URI = "$ID.$INTENT_FILTER.EXTRA_URI"
-        const val EXTRA_TIME = "$ID.$INTENT_FILTER.EXTRA_TIME"
-        const val EXTRA_ERROR_FILE_PATH = "$ID.$INTENT_FILTER.EXTRA_ERROR_FILE_PATH"
-        const val EXTRA_ERROR_FILE = "$ID.$INTENT_FILTER.EXTRA_ERROR_FILE"
-
-        private const val BACKUP_CREATE = 201
-        private const val BACKUP_RESTORE = 202
-        private const val BACKUP_DIR = 203
-
-        fun newInstance(rootKey: String): SettingsBackupFragment {
-            val args = Bundle()
-            args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
-            return SettingsBackupFragment().apply { arguments = args }
-        }
-    }
-
-    /**
-     * Preference selected to create backup
-     */
-    private val createBackup: Preference by bindPref(R.string.pref_create_local_backup_key)
-
-    /**
-     * Preference selected to restore backup
-     */
-    private val restoreBackup: Preference by bindPref(R.string.pref_restore_local_backup_key)
-
-    /**
-     * Preference which determines the frequency of automatic backups.
-     */
-    private val automaticBackup: IntListPreference by bindPref(R.string.pref_backup_interval_key)
-
-    /**
-     * Preference containing number of automatic backups
-     */
-    private val backupSlots: IntListPreference by bindPref(R.string.pref_backup_slots_key)
-
-    /**
-     * Preference containing interval of automatic backups
-     */
-    private val backupDirPref: Preference by bindPref(R.string.pref_backup_directory_key)
-
-    /**
-     * Preferences
-     */
-    private val preferences: PreferencesHelper by injectLazy()
-
-    /**
-     * Value containing information on what to backup
-     */
-    private var backup_flags = 0
-
-    /**
-     * The root directory for backups..
-     */
-    private var backupDir = preferences.backupsDirectory().getOrDefault().let {
-        UniFile.fromUri(context, Uri.parse(it))
-    }
-
-    val restoreDialog: MaterialDialog by lazy {
-        MaterialDialog.Builder(context)
-                .title(R.string.backup)
-                .content(R.string.restoring_backup)
-                .progress(false, 100, true)
-                .cancelable(false)
-                .negativeText(R.string.action_stop)
-                .onNegative { materialDialog, _ ->
-                    BackupRestoreService.stop(context)
-                    materialDialog.dismiss()
-                }
-                .build()
-    }
-
-    val backupDialog: MaterialDialog by lazy {
-        MaterialDialog.Builder(context)
-                .title(R.string.backup)
-                .content(R.string.creating_backup)
-                .progress(true, 0)
-                .cancelable(false)
-                .build()
-    }
-
-    private val receiver = object : BroadcastReceiver() {
-
-        override fun onReceive(context: Context, intent: Intent) {
-            when (intent.getStringExtra(ACTION)) {
-                ACTION_BACKUP_COMPLETED_DIALOG -> {
-                    backupDialog.dismiss()
-                    val uri = Uri.parse(intent.getStringExtra(EXTRA_URI))
-                    val file = UniFile.fromUri(context, uri)
-                    MaterialDialog.Builder([email protected])
-                            .title(getString(R.string.backup_created))
-                            .content(getString(R.string.file_saved, file.filePath))
-                            .positiveText(getString(R.string.action_close))
-                            .negativeText(getString(R.string.action_export))
-                            .onPositive { materialDialog, _ -> materialDialog.dismiss() }
-                            .onNegative { _, _ ->
-                                val sendIntent = Intent(Intent.ACTION_SEND)
-                                sendIntent.type = "application/json"
-                                sendIntent.putExtra(Intent.EXTRA_STREAM, file.uri)
-                                startActivity(Intent.createChooser(sendIntent, ""))
-                            }
-                            .safeShow()
-
-                }
-                ACTION_SET_PROGRESS_DIALOG -> {
-                    val progress = intent.getIntExtra(EXTRA_PROGRESS, 0)
-                    val amount = intent.getIntExtra(EXTRA_AMOUNT, 0)
-                    val content = intent.getStringExtra(EXTRA_CONTENT)
-                    restoreDialog.setContent(content)
-                    restoreDialog.setProgress(progress)
-                    restoreDialog.maxProgress = amount
-                }
-                ACTION_RESTORE_COMPLETED_DIALOG -> {
-                    restoreDialog.dismiss()
-                    val time = intent.getLongExtra(EXTRA_TIME, 0)
-                    val errors = intent.getIntExtra(EXTRA_ERRORS, 0)
-                    val path = intent.getStringExtra(EXTRA_ERROR_FILE_PATH)
-                    val file = intent.getStringExtra(EXTRA_ERROR_FILE)
-                    val timeString = String.format("%02d min, %02d sec",
-                            TimeUnit.MILLISECONDS.toMinutes(time),
-                            TimeUnit.MILLISECONDS.toSeconds(time) -
-                                    TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(time))
-                    )
-
-                    if (errors > 0) {
-                        MaterialDialog.Builder([email protected])
-                                .title(getString(R.string.restore_completed))
-                                .content(getString(R.string.restore_completed_content, timeString,
-                                        if (errors > 0) "$errors" else getString(android.R.string.no)))
-                                .positiveText(getString(R.string.action_close))
-                                .negativeText(getString(R.string.action_open_log))
-                                .onPositive { materialDialog, _ -> materialDialog.dismiss() }
-                                .onNegative { materialDialog, _ ->
-                                    if (!path.isEmpty()) {
-                                        val destFile = File(path, file)
-                                        val uri = destFile.getUriCompat(context)
-                                        val sendIntent = Intent(Intent.ACTION_VIEW).apply {
-                                            setDataAndType(uri, "text/plain")
-                                            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
-                                        }
-                                        startActivity(sendIntent)
-                                    } else {
-                                        context.toast(getString(R.string.error_opening_log))
-                                    }
-                                    materialDialog.dismiss()
-                                }
-                                .safeShow()
-                    }
-                }
-                ACTION_ERROR_BACKUP_DIALOG -> {
-                    context.toast(intent.getStringExtra(EXTRA_ERROR_MESSAGE))
-                    backupDialog.dismiss()
-                }
-                ACTION_ERROR_RESTORE_DIALOG -> {
-                    context.toast(intent.getStringExtra(EXTRA_ERROR_MESSAGE))
-                    restoreDialog.dismiss()
-                }
-            }
-        }
-
-    }
-
-    override fun onStart() {
-        super.onStart()
-        context.registerLocalReceiver(receiver, IntentFilter(INTENT_FILTER))
-    }
-
-    override fun onPause() {
-        context.unregisterLocalReceiver(receiver)
-        super.onPause()
-    }
-
-    override fun onViewCreated(view: View, savedState: Bundle?) {
-        super.onViewCreated(view, savedState)
-
-        if (savedState != null) {
-            if (BackupRestoreService.isRunning(context)) {
-                restoreDialog.safeShow()
-            }
-            else if (BackupCreateService.isRunning(context)) {
-                backupDialog.safeShow()
-            }
-        }
-
-        (activity as BaseActivity).requestPermissionsOnMarshmallow()
-
-        // Set onClickListeners
-        createBackup.setOnPreferenceClickListener {
-            MaterialDialog.Builder(context)
-                    .title(R.string.pref_create_backup)
-                    .content(R.string.backup_choice)
-                    .items(R.array.backup_options)
-                    .itemsCallbackMultiChoice(arrayOf(0, 1, 2, 3, 4 /*todo not hard code*/)) { _, positions, _ ->
-                        // TODO not very happy with global value, but putExtra doesn't work
-                        backup_flags = 0
-                        for (i in 1..positions.size - 1) {
-                            when (positions[i]) {
-                                1 -> backup_flags = backup_flags or BackupCreateService.BACKUP_CATEGORY
-                                2 -> backup_flags = backup_flags or BackupCreateService.BACKUP_CHAPTER
-                                3 -> backup_flags = backup_flags or BackupCreateService.BACKUP_TRACK
-                                4 -> backup_flags = backup_flags or BackupCreateService.BACKUP_HISTORY
-                            }
-                        }
-                        // If API lower as KitKat use custom dir picker
-                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
-                            // Get dirs
-                            val currentDir = preferences.backupsDirectory().getOrDefault()
-
-                            val i = Intent(activity, CustomLayoutPickerActivity::class.java)
-                            i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
-                            i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
-                            i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
-                            i.putExtra(FilePickerActivity.EXTRA_START_PATH, currentDir)
-                            startActivityForResult(i, BACKUP_CREATE)
-                        } else {
-                            // Use Androids build in file creator
-                            val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
-                            intent.addCategory(Intent.CATEGORY_OPENABLE)
-
-                            // TODO create custom MIME data type? Will make older backups deprecated
-                            intent.type = "application/*"
-                            intent.putExtra(Intent.EXTRA_TITLE, Backup.getDefaultFilename())
-                            startActivityForResult(intent, BACKUP_CREATE)
-                        }
-                        true
-                    }
-                    .itemsDisabledIndices(0)
-                    .positiveText(getString(R.string.action_create))
-                    .negativeText(android.R.string.cancel)
-                    .safeShow()
-            true
-        }
-
-        restoreBackup.setOnPreferenceClickListener {
-            val intent = Intent(Intent.ACTION_GET_CONTENT)
-            intent.addCategory(Intent.CATEGORY_OPENABLE)
-            intent.type = "application/*"
-            val chooser = Intent.createChooser(intent, getString(R.string.file_select_backup))
-            startActivityForResult(chooser, BACKUP_RESTORE)
-            true
-        }
-
-        automaticBackup.setOnPreferenceChangeListener { _, newValue ->
-            // Always cancel the previous task, it seems that sometimes they are not updated.
-            BackupCreatorJob.cancelTask()
-
-            val interval = (newValue as String).toInt()
-            if (interval > 0) {
-                BackupCreatorJob.setupTask(interval)
-            }
-            true
-        }
-
-        backupSlots.setOnPreferenceChangeListener { preference, newValue ->
-            preferences.numberOfBackups().set((newValue as String).toInt())
-            preference.summary = newValue
-            true
-        }
-
-        backupDirPref.setOnPreferenceClickListener {
-            val currentDir = preferences.backupsDirectory().getOrDefault()
-
-            if (Build.VERSION.SDK_INT < 21) {
-                // Custom dir selected, open directory selector
-                val i = Intent(activity, CustomLayoutPickerActivity::class.java)
-                i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
-                i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
-                i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
-                i.putExtra(FilePickerActivity.EXTRA_START_PATH, currentDir)
-
-                startActivityForResult(i, BACKUP_DIR)
-            } else {
-                val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
-                startActivityForResult(i, BACKUP_DIR)
-            }
-
-            true
-        }
-
-        subscriptions += preferences.backupsDirectory().asObservable()
-                .subscribe { path ->
-                    backupDir = UniFile.fromUri(context, Uri.parse(path))
-                    backupDirPref.summary = backupDir.filePath ?: path
-                }
-
-        subscriptions += preferences.backupInterval().asObservable()
-                .subscribe {
-                    backupDirPref.isVisible = it > 0
-                    backupSlots.isVisible = it > 0
-                }
-    }
-
-    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        when (requestCode) {
-            BACKUP_DIR -> if (data != null && resultCode == Activity.RESULT_OK) {
-                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
-                    val uri = Uri.fromFile(File(data.data.path))
-                    preferences.backupsDirectory().set(uri.toString())
-                } else {
-                    val uri = data.data
-                    val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
-                            Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-
-                    context.contentResolver.takePersistableUriPermission(uri, flags)
-
-                    val file = UniFile.fromUri(context, uri)
-                    preferences.backupsDirectory().set(file.uri.toString())
-                }
-            }
-            BACKUP_CREATE -> if (data != null && resultCode == Activity.RESULT_OK) {
-                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
-                    val dir = data.data.path
-                    val file = File(dir, Backup.getDefaultFilename())
-
-                    backupDialog.safeShow()
-                    BackupCreateService.makeBackup(context, file.toURI().toString(), backup_flags)
-                } else {
-                    val uri = data.data
-                    val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
-                            Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-
-                    context.contentResolver.takePersistableUriPermission(uri, flags)
-                    val file = UniFile.fromUri(context, uri)
-
-                    backupDialog.safeShow()
-                    BackupCreateService.makeBackup(context, file.uri.toString(), backup_flags)
-                }
-            }
-            BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) {
-                val uri = data.data
-
-                MaterialDialog.Builder(context)
-                        .title(getString(R.string.pref_restore_backup))
-                        .content(getString(R.string.backup_restore_content))
-                        .positiveText(getString(R.string.action_restore))
-                        .onPositive { _, _ ->
-                            restoreDialog.safeShow()
-                            BackupRestoreService.start(context, uri)
-                        }
-                        .safeShow()
-            }
-        }
-    }
-
-    fun MaterialDialog.Builder.safeShow(): Dialog {
-        return build().safeShow()
-    }
-
-    fun Dialog.safeShow(): Dialog {
-        subscriptions += Subscriptions.create { dismiss() }
-        show()
-        return this
-    }
-
-}

+ 70 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt

@@ -0,0 +1,70 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.content.Context
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import android.support.v7.preference.PreferenceController
+import android.support.v7.preference.PreferenceScreen
+import android.util.TypedValue
+import android.view.ContextThemeWrapper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import rx.Observable
+import rx.Subscription
+import rx.subscriptions.CompositeSubscription
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+
+abstract class SettingsController : PreferenceController() {
+
+    val preferences: PreferencesHelper = Injekt.get()
+
+    var untilDestroySubscriptions = CompositeSubscription()
+        private set
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View {
+        if (untilDestroySubscriptions.isUnsubscribed) {
+            untilDestroySubscriptions = CompositeSubscription()
+        }
+        return super.onCreateView(inflater, container, savedInstanceState)
+    }
+
+    override fun onDestroyView(view: View) {
+        super.onDestroyView(view)
+        untilDestroySubscriptions.unsubscribe()
+    }
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        val screen = preferenceManager.createPreferenceScreen(getThemedContext())
+        preferenceScreen = screen
+        setupPreferenceScreen(screen)
+    }
+
+    abstract fun setupPreferenceScreen(screen: PreferenceScreen): Any?
+
+    private fun getThemedContext(): Context {
+        val tv = TypedValue()
+        activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
+        return ContextThemeWrapper(activity, tv.resourceId)
+    }
+
+    open fun getTitle(): String? {
+        return preferenceScreen?.title?.toString()
+    }
+
+    override fun onAttach(view: View) {
+        (activity as? AppCompatActivity)?.supportActionBar?.title = getTitle()
+        super.onAttach(view)
+    }
+
+    fun <T> Observable<T>.subscribeUntilDestroy(): Subscription {
+        return subscribe().also { untilDestroySubscriptions.add(it) }
+    }
+
+    fun <T> Observable<T>.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription {
+        return subscribe(onNext).also { untilDestroySubscriptions.add(it) }
+    }
+}

+ 186 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt

@@ -0,0 +1,186 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.support.v4.content.ContextCompat
+import android.support.v7.preference.PreferenceScreen
+import com.afollestad.materialdialogs.MaterialDialog
+import com.hippo.unifile.UniFile
+import com.nononsenseapps.filepicker.FilePickerActivity
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.util.DiskUtil
+import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
+import uy.kohesive.injekt.injectLazy
+import java.io.File
+import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
+
+class SettingsDownloadController : SettingsController() {
+
+    private val db: DatabaseHelper by injectLazy()
+
+    override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
+        titleRes = R.string.pref_category_downloads
+
+        preference {
+            key = Keys.downloadsDirectory
+            titleRes = R.string.pref_download_directory
+            onClick {
+                showDownloadDirectoriesDialog()
+            }
+
+            preferences.downloadsDirectory().asObservable()
+                    .subscribeUntilDestroy { path ->
+                        val dir = UniFile.fromUri(context, Uri.parse(path))
+                        summary = dir.filePath ?: path
+
+                        // Don't display downloaded chapters in gallery apps creating .nomedia
+                        if (dir != null && dir.exists()) {
+                            val nomedia = dir.findFile(".nomedia")
+                            if (nomedia == null) {
+                                dir.createFile(".nomedia")
+                                applicationContext?.let { DiskUtil.scanMedia(it, dir.uri) }
+                            }
+                        }
+                    }
+        }
+        switchPreference {
+            key = Keys.downloadOnlyOverWifi
+            titleRes = R.string.pref_download_only_over_wifi
+            defaultValue = true
+        }
+        intListPreference {
+            key = Keys.downloadThreads
+            titleRes = R.string.pref_download_slots
+            entries = arrayOf("1", "2", "3")
+            entryValues = arrayOf("1", "2", "3")
+            defaultValue = "1"
+            summary = "%s"
+        }
+        preferenceCategory {
+            titleRes = R.string.pref_remove_after_read
+
+            switchPreference {
+                key = Keys.removeAfterMarkedAsRead
+                titleRes = R.string.pref_remove_after_marked_as_read
+                defaultValue = false
+            }
+            intListPreference {
+                key = Keys.removeAfterReadSlots
+                titleRes = R.string.pref_remove_after_read
+                entriesRes = arrayOf(R.string.disabled, R.string.last_read_chapter,
+                        R.string.second_to_last, R.string.third_to_last, R.string.fourth_to_last,
+                        R.string.fifth_to_last)
+                entryValues = arrayOf("-1", "0", "1", "2", "3", "4")
+                defaultValue = "-1"
+                summary = "%s"
+            }
+        }
+
+        val dbCategories = db.getCategories().executeAsBlocking()
+
+        preferenceCategory {
+            titleRes = R.string.pref_download_new
+
+            switchPreference {
+                key = Keys.downloadNew
+                titleRes = R.string.pref_download_new
+                defaultValue = false
+            }
+            multiSelectListPreference {
+                key = Keys.downloadNewCategories
+                titleRes = R.string.pref_download_new_categories
+                entries = dbCategories.map { it.name }.toTypedArray()
+                entryValues = dbCategories.map { it.id.toString() }.toTypedArray()
+
+                preferences.downloadNew().asObservable()
+                        .subscribeUntilDestroy { isVisible = it }
+
+                preferences.downloadNewCategories().asObservable()
+                        .subscribe {
+                            val selectedCategories = it
+                                    .mapNotNull { id -> dbCategories.find { it.id == id.toInt() } }
+                                    .sortedBy { it.order }
+
+                            summary = if (selectedCategories.isEmpty())
+                                resources?.getString(R.string.all)
+                            else
+                                selectedCategories.joinToString { it.name }
+                        }
+            }
+        }
+    }
+
+    private fun showDownloadDirectoriesDialog() {
+        val activity = activity ?: return
+
+        val currentDir = preferences.downloadsDirectory().getOrDefault()
+        val externalDirs = getExternalFilesDirs() + File(activity.getString(R.string.custom_dir))
+        val selectedIndex = externalDirs.map(File::toString).indexOfFirst { it in currentDir }
+
+        MaterialDialog.Builder(activity)
+                .items(externalDirs)
+                .itemsCallbackSingleChoice(selectedIndex, { _, _, which, text ->
+                    if (which == externalDirs.lastIndex) {
+                        if (Build.VERSION.SDK_INT < 21) {
+                            // Custom dir selected, open directory selector
+                            val i = Intent(activity, CustomLayoutPickerActivity::class.java)
+                            i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
+                            i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
+                            i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
+                            i.putExtra(FilePickerActivity.EXTRA_START_PATH, currentDir)
+
+                            startActivityForResult(i, DOWNLOAD_DIR_PRE_L)
+                        } else {
+                            val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+                            startActivityForResult(i, DOWNLOAD_DIR_L)
+                        }
+                    } else {
+                        // One of the predefined folders was selected
+                        val path = Uri.fromFile(File(text.toString()))
+                        preferences.downloadsDirectory().set(path.toString())
+                    }
+                    true
+                })
+                .show()
+    }
+
+    private fun getExternalFilesDirs(): List<File> {
+        val defaultDir = Environment.getExternalStorageDirectory().absolutePath +
+                File.separator + resources?.getString(R.string.app_name) +
+                File.separator + "downloads"
+
+        return mutableListOf(File(defaultDir)) +
+                ContextCompat.getExternalFilesDirs(activity, "").filterNotNull()
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        when (requestCode) {
+            DOWNLOAD_DIR_PRE_L -> if (data != null && resultCode == Activity.RESULT_OK) {
+                val uri = Uri.fromFile(File(data.data.path))
+                preferences.downloadsDirectory().set(uri.toString())
+            }
+            DOWNLOAD_DIR_L -> if (data != null && resultCode == Activity.RESULT_OK) {
+                val context = applicationContext ?: return
+                val uri = data.data
+                val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
+                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+
+                @Suppress("NewApi")
+                context.contentResolver.takePersistableUriPermission(uri, flags)
+
+                val file = UniFile.fromUri(context, uri)
+                preferences.downloadsDirectory().set(file.uri.toString())
+            }
+        }
+    }
+
+    private companion object {
+        const val DOWNLOAD_DIR_PRE_L = 103
+        const val DOWNLOAD_DIR_L = 104
+    }
+}

+ 0 - 149
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadsFragment.kt

@@ -1,149 +0,0 @@
-package eu.kanade.tachiyomi.ui.setting
-
-import android.app.Activity
-import android.content.Intent
-import android.net.Uri
-import android.os.Build
-import android.os.Bundle
-import android.os.Environment
-import android.support.v4.content.ContextCompat
-import android.support.v7.preference.Preference
-import android.support.v7.preference.XpPreferenceFragment
-import android.view.View
-import com.afollestad.materialdialogs.MaterialDialog
-import com.hippo.unifile.UniFile
-import com.nononsenseapps.filepicker.FilePickerActivity
-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.util.plusAssign
-import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
-import net.xpece.android.support.preference.MultiSelectListPreference
-import uy.kohesive.injekt.injectLazy
-import java.io.File
-
-class SettingsDownloadsFragment : SettingsFragment() {
-
-    companion object {
-        const val DOWNLOAD_DIR_PRE_L = 103
-        const val DOWNLOAD_DIR_L = 104
-
-        fun newInstance(rootKey: String): SettingsDownloadsFragment {
-            val args = Bundle()
-            args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
-            return SettingsDownloadsFragment().apply { arguments = args }
-        }
-    }
-
-    private val preferences: PreferencesHelper by injectLazy()
-
-    private val db: DatabaseHelper by injectLazy()
-
-    val downloadDirPref: Preference by bindPref(R.string.pref_download_directory_key)
-
-    val downloadCategory: MultiSelectListPreference by bindPref(R.string.pref_download_new_categories_key)
-
-    override fun onViewCreated(view: View, savedState: Bundle?) {
-        super.onViewCreated(view, savedState)
-
-        downloadDirPref.setOnPreferenceClickListener {
-
-            val currentDir = preferences.downloadsDirectory().getOrDefault()
-            val externalDirs = getExternalFilesDirs() + File(getString(R.string.custom_dir))
-            val selectedIndex = externalDirs.map(File::toString).indexOfFirst { it in currentDir }
-
-            MaterialDialog.Builder(activity)
-                    .items(externalDirs)
-                    .itemsCallbackSingleChoice(selectedIndex, { dialog, view, which, text ->
-                        if (which == externalDirs.lastIndex) {
-                            if (Build.VERSION.SDK_INT < 21) {
-                                // Custom dir selected, open directory selector
-                                val i = Intent(activity, CustomLayoutPickerActivity::class.java)
-                                i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
-                                i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
-                                i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
-                                i.putExtra(FilePickerActivity.EXTRA_START_PATH, currentDir)
-
-                                startActivityForResult(i, DOWNLOAD_DIR_PRE_L)
-                            } else {
-                                val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
-                                startActivityForResult(i, DOWNLOAD_DIR_L)
-                            }
-                        } else {
-                            // One of the predefined folders was selected
-                            val path = Uri.fromFile(File(text.toString()))
-                            preferences.downloadsDirectory().set(path.toString())
-                        }
-                        true
-                    })
-                    .show()
-
-            true
-        }
-
-        subscriptions += preferences.downloadsDirectory().asObservable()
-                .subscribe { path ->
-                    val dir = UniFile.fromUri(context, Uri.parse(path))
-
-                    downloadDirPref.summary = dir.filePath ?: path
-
-                    // Don't display downloaded chapters in gallery apps creating a ".nomedia" file.
-                    if (dir != null && dir.exists()) {
-                        dir.createFile(".nomedia")
-                    }
-                }
-
-        subscriptions += preferences.downloadNew().asObservable()
-                .subscribe { downloadCategory.isVisible = it }
-
-        val dbCategories = db.getCategories().executeAsBlocking()
-        downloadCategory.apply {
-            entries = dbCategories.map { it.name }.toTypedArray()
-            entryValues = dbCategories.map { it.id.toString() }.toTypedArray()
-        }
-
-        subscriptions += preferences.downloadNewCategories().asObservable()
-                .subscribe {
-                    val selectedCategories = it
-                            .mapNotNull { id -> dbCategories.find { it.id == id.toInt() } }
-                            .sortedBy { it.order }
-
-                    val summary = if (selectedCategories.isEmpty())
-                        getString(R.string.all)
-                    else
-                        selectedCategories.joinToString { it.name }
-
-                    downloadCategory.summary = summary
-                }
-    }
-
-    fun getExternalFilesDirs(): List<File> {
-        val defaultDir = Environment.getExternalStorageDirectory().absolutePath +
-                File.separator + getString(R.string.app_name) +
-                File.separator + "downloads"
-
-        return mutableListOf(File(defaultDir)) +
-                ContextCompat.getExternalFilesDirs(activity, "").filterNotNull()
-    }
-
-    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        when (requestCode) {
-            DOWNLOAD_DIR_PRE_L -> if (data != null && resultCode == Activity.RESULT_OK) {
-                val uri = Uri.fromFile(File(data.data.path))
-                preferences.downloadsDirectory().set(uri.toString())
-            }
-            DOWNLOAD_DIR_L -> if (data != null && resultCode == Activity.RESULT_OK) {
-                val uri = data.data
-                val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
-                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-
-                @Suppress("NewApi")
-                context.contentResolver.takePersistableUriPermission(uri, flags)
-
-                val file = UniFile.fromUri(context, uri)
-                preferences.downloadsDirectory().set(file.uri.toString())
-            }
-        }
-    }
-}

+ 0 - 62
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt

@@ -1,62 +0,0 @@
-package eu.kanade.tachiyomi.ui.setting
-
-import android.os.Bundle
-import android.support.annotation.CallSuper
-import android.support.v7.preference.Preference
-import android.support.v7.preference.XpPreferenceFragment
-import android.view.View
-import eu.kanade.tachiyomi.R
-import net.xpece.android.support.preference.PreferenceScreenNavigationStrategy
-import rx.subscriptions.CompositeSubscription
-
-open class SettingsFragment : XpPreferenceFragment() {
-
-    companion object {
-        fun newInstance(rootKey: String?): SettingsFragment {
-            val args = Bundle()
-            args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
-            return SettingsFragment().apply { arguments = args }
-        }
-    }
-
-    lateinit var subscriptions: CompositeSubscription
-
-    override final fun onCreatePreferences2(savedState: Bundle?, rootKey: String?) {
-        subscriptions = CompositeSubscription()
-
-        addPreferencesFromResource(R.xml.pref_general)
-        addPreferencesFromResource(R.xml.pref_reader)
-        addPreferencesFromResource(R.xml.pref_downloads)
-        addPreferencesFromResource(R.xml.pref_sources)
-        addPreferencesFromResource(R.xml.pref_tracking)
-        addPreferencesFromResource(R.xml.pref_backup)
-        addPreferencesFromResource(R.xml.pref_advanced)
-        addPreferencesFromResource(R.xml.pref_about)
-
-        // Setup root preference title.
-        preferenceScreen.title = activity.title
-
-        PreferenceScreenNavigationStrategy.ReplaceFragment.onCreatePreferences(this, rootKey)
-    }
-
-    @CallSuper
-    override fun onViewCreated(view: View, savedState: Bundle?) {
-        super.onViewCreated(view, savedState)
-        listView.isFocusable = false
-    }
-
-    override fun onStart() {
-        super.onStart()
-        activity.title = preferenceScreen.title
-    }
-
-    override fun onDestroyView() {
-        subscriptions.unsubscribe()
-        super.onDestroyView()
-    }
-
-    protected inline fun <reified T : Preference> bindPref(resId: Int): Lazy<T> {
-        return lazy { findPreference(getString(resId)) as T }
-    }
-
-}

+ 225 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt

@@ -0,0 +1,225 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.app.Dialog
+import android.os.Bundle
+import android.os.Handler
+import android.support.v7.preference.PreferenceScreen
+import android.view.View
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.ui.base.controller.DialogController
+import eu.kanade.tachiyomi.util.LocaleHelper
+import kotlinx.android.synthetic.main.pref_library_columns.view.*
+import rx.Observable
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
+
+class SettingsGeneralController : SettingsController() {
+
+    private val db: DatabaseHelper = Injekt.get()
+
+    override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
+        titleRes = R.string.pref_category_general
+
+        listPreference {
+            key = Keys.lang
+            titleRes = R.string.pref_language
+            entryValues = arrayOf("", "bg", "en", "es", "fr", "it", "pt", "ru", "vi")
+            entries = entryValues.map { value ->
+                val locale = LocaleHelper.getLocaleFromString(value.toString())
+                locale?.getDisplayName(locale)?.capitalize() ?:
+                        context.getString(R.string.system_default)
+            }.toTypedArray()
+            defaultValue = ""
+            summary = "%s"
+
+            onChange { newValue ->
+                val activity = activity ?: return@onChange false
+                val app = activity.application
+                LocaleHelper.changeLocale(newValue.toString())
+                LocaleHelper.updateConfiguration(app, app.resources.configuration)
+                activity.recreate()
+                true
+            }
+        }
+        intListPreference {
+            key = Keys.theme
+            titleRes = R.string.pref_theme
+            entriesRes = arrayOf(R.string.light_theme, R.string.dark_theme)
+            entryValues = arrayOf("1", "2")
+            defaultValue = "1"
+            summary = "%s"
+
+            onChange {
+                activity?.recreate()
+                true
+            }
+        }
+        preference {
+            titleRes = R.string.pref_library_columns
+            onClick {
+                LibraryColumnsDialog().showDialog(router)
+            }
+
+            fun getColumnValue(value: Int): String {
+                return if (value == 0)
+                    context.getString(R.string.default_columns)
+                else
+                    value.toString()
+            }
+
+            Observable.combineLatest(
+                    preferences.portraitColumns().asObservable(),
+                    preferences.landscapeColumns().asObservable(),
+                    { portraitCols, landscapeCols -> Pair(portraitCols, landscapeCols) })
+                    .subscribeUntilDestroy { (portraitCols, landscapeCols) ->
+                        val portrait = getColumnValue(portraitCols)
+                        val landscape = getColumnValue(landscapeCols)
+                        summary = "${context.getString(R.string.portrait)}: $portrait, " +
+                                "${context.getString(R.string.landscape)}: $landscape"
+                    }
+        }
+        intListPreference {
+            key = Keys.startScreen
+            titleRes = R.string.pref_start_screen
+            entriesRes = arrayOf(R.string.label_library, R.string.label_recent_manga,
+                    R.string.label_recent_updates)
+            entryValues = arrayOf("1", "2", "3")
+            defaultValue = "1"
+            summary = "%s"
+        }
+        intListPreference {
+            key = Keys.libraryUpdateInterval
+            titleRes = R.string.pref_library_update_interval
+            entriesRes = arrayOf(R.string.update_never, R.string.update_1hour,
+                    R.string.update_2hour, R.string.update_3hour, R.string.update_6hour,
+                    R.string.update_12hour, R.string.update_24hour, R.string.update_48hour)
+            entryValues = arrayOf("0", "1", "2", "3", "6", "12", "24", "48")
+            defaultValue = "0"
+            summary = "%s"
+
+            onChange { newValue ->
+                // Always cancel the previous task, it seems that sometimes they are not updated.
+                LibraryUpdateJob.cancelTask()
+
+                val interval = (newValue as String).toInt()
+                if (interval > 0) {
+                    LibraryUpdateJob.setupTask(interval)
+                }
+                true
+            }
+        }
+        multiSelectListPreference {
+            key = Keys.libraryUpdateRestriction
+            titleRes = R.string.pref_library_update_restriction
+            entriesRes = arrayOf(R.string.wifi, R.string.charging)
+            entryValues = arrayOf("wifi", "ac")
+            summaryRes = R.string.pref_library_update_restriction_summary
+
+            preferences.libraryUpdateInterval().asObservable()
+                    .subscribeUntilDestroy { isVisible = it > 0 }
+
+            onChange {
+                // Post to event looper to allow the preference to be updated.
+                Handler().post { LibraryUpdateJob.setupTask() }
+                true
+            }
+        }
+        switchPreference {
+            key = Keys.updateOnlyNonCompleted
+            titleRes = R.string.pref_update_only_non_completed
+            defaultValue = false
+        }
+
+        val dbCategories = db.getCategories().executeAsBlocking()
+
+        multiSelectListPreference {
+            key = Keys.libraryUpdateCategories
+            titleRes = R.string.pref_library_update_categories
+            entries = dbCategories.map { it.name }.toTypedArray()
+            entryValues = dbCategories.map { it.id.toString() }.toTypedArray()
+
+            preferences.libraryUpdateCategories().asObservable()
+                    .subscribeUntilDestroy {
+                        val selectedCategories = it
+                                .mapNotNull { id -> dbCategories.find { it.id == id.toInt() } }
+                                .sortedBy { it.order }
+
+                        summary = if (selectedCategories.isEmpty())
+                            context.getString(R.string.all)
+                        else
+                            selectedCategories.joinToString { it.name }
+                    }
+        }
+        intListPreference {
+            key = Keys.defaultCategory
+            titleRes = R.string.default_category
+
+            val selectedCategory = dbCategories.find { it.id == preferences.defaultCategory() }
+            entries = arrayOf(context.getString(R.string.default_category_summary)) +
+                    dbCategories.map { it.name }.toTypedArray()
+            entryValues = arrayOf("-1") + dbCategories.map { it.id.toString() }.toTypedArray()
+            defaultValue = "-1"
+            summary = selectedCategory?.name ?: context.getString(R.string.default_category_summary)
+
+            onChange { newValue ->
+                summary = dbCategories.find {
+                    it.id == (newValue as String).toInt()
+                }?.name ?: context.getString(R.string.default_category_summary)
+                true
+            }
+        }
+    }
+
+    class LibraryColumnsDialog : DialogController() {
+
+        private val preferences: PreferencesHelper = Injekt.get()
+
+        private var portrait = preferences.portraitColumns().getOrDefault()
+        private var landscape = preferences.landscapeColumns().getOrDefault()
+
+        override fun onCreateDialog(savedViewState: Bundle?): Dialog {
+            val dialog = MaterialDialog.Builder(activity!!)
+                    .title(R.string.pref_library_columns)
+                    .customView(R.layout.pref_library_columns, false)
+                    .positiveText(android.R.string.ok)
+                    .negativeText(android.R.string.cancel)
+                    .onPositive { _, _ ->
+                        preferences.portraitColumns().set(portrait)
+                        preferences.landscapeColumns().set(landscape)
+                    }
+                    .build()
+
+            onViewCreated(dialog.view)
+            return dialog
+        }
+
+        fun onViewCreated(view: View) {
+            with(view.portrait_columns) {
+                displayedValues = arrayOf(context.getString(R.string.default_columns)) +
+                        IntRange(1, 10).map(Int::toString)
+                value = portrait
+
+                setOnValueChangedListener { _, _, newValue ->
+                    portrait = newValue
+                }
+            }
+            with(view.landscape_columns) {
+                displayedValues = arrayOf(context.getString(R.string.default_columns)) +
+                        IntRange(1, 10).map(Int::toString)
+                value = landscape
+
+                setOnValueChangedListener { _, _, newValue ->
+                    landscape = newValue
+                }
+            }
+        }
+
+    }
+
+}

+ 0 - 166
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt

@@ -1,166 +0,0 @@
-package eu.kanade.tachiyomi.ui.setting
-
-import android.os.Bundle
-import android.support.v7.preference.Preference
-import android.support.v7.preference.PreferenceFragmentCompat
-import android.support.v7.preference.XpPreferenceFragment
-import android.view.View
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.database.DatabaseHelper
-import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
-import eu.kanade.tachiyomi.data.preference.PreferencesHelper
-import eu.kanade.tachiyomi.util.LocaleHelper
-import eu.kanade.tachiyomi.util.plusAssign
-import eu.kanade.tachiyomi.widget.preference.IntListPreference
-import eu.kanade.tachiyomi.widget.preference.LibraryColumnsDialog
-import eu.kanade.tachiyomi.widget.preference.SimpleDialogPreference
-import net.xpece.android.support.preference.ListPreference
-import net.xpece.android.support.preference.MultiSelectListPreference
-import rx.Observable
-import rx.android.schedulers.AndroidSchedulers
-import uy.kohesive.injekt.injectLazy
-
-class SettingsGeneralFragment : SettingsFragment(),
-        PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback {
-
-
-    companion object {
-        fun newInstance(rootKey: String): SettingsGeneralFragment {
-            val args = Bundle()
-            args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
-            return SettingsGeneralFragment().apply { arguments = args }
-        }
-    }
-
-    private val preferences: PreferencesHelper by injectLazy()
-
-    private val db: DatabaseHelper by injectLazy()
-
-    val columnsPreference: SimpleDialogPreference by bindPref(R.string.pref_library_columns_dialog_key)
-
-    val updateInterval: IntListPreference by bindPref(R.string.pref_library_update_interval_key)
-
-    val updateRestriction: MultiSelectListPreference by bindPref(R.string.pref_library_update_restriction_key)
-
-    val themePreference: IntListPreference by bindPref(R.string.pref_theme_key)
-
-    val categoryUpdate: MultiSelectListPreference by bindPref(R.string.pref_library_update_categories_key)
-
-    val defaultCategory: IntListPreference by bindPref(R.string.default_category_key)
-
-    val langPreference: ListPreference by bindPref(R.string.pref_language_key)
-
-    override fun onViewCreated(view: View, savedState: Bundle?) {
-        super.onViewCreated(view, savedState)
-
-        subscriptions += preferences.libraryUpdateInterval().asObservable()
-                .subscribe { updateRestriction.isVisible = it > 0 }
-
-        subscriptions += Observable.combineLatest(
-                preferences.portraitColumns().asObservable(),
-                preferences.landscapeColumns().asObservable())
-                { portraitColumns, landscapeColumns -> Pair(portraitColumns, landscapeColumns) }
-                .subscribe { updateColumnsSummary(it.first, it.second) }
-
-        updateInterval.setOnPreferenceChangeListener { preference, newValue ->
-            // Always cancel the previous task, it seems that sometimes they are not updated.
-            LibraryUpdateJob.cancelTask()
-
-            val interval = (newValue as String).toInt()
-            if (interval > 0) {
-                LibraryUpdateJob.setupTask(interval)
-            }
-            true
-        }
-
-        updateRestriction.setOnPreferenceChangeListener { preference, newValue ->
-            // Post to event looper to allow the preference to be updated.
-            subscriptions += Observable.fromCallable {
-                LibraryUpdateJob.setupTask()
-            }.subscribeOn(AndroidSchedulers.mainThread()).subscribe()
-
-            true
-        }
-
-        val dbCategories = db.getCategories().executeAsBlocking()
-        categoryUpdate.apply {
-            entries = dbCategories.map { it.name }.toTypedArray()
-            entryValues = dbCategories.map { it.id.toString() }.toTypedArray()
-        }
-
-        subscriptions += preferences.libraryUpdateCategories().asObservable()
-                .subscribe {
-                    val selectedCategories = it
-                            .mapNotNull { id -> dbCategories.find { it.id == id.toInt() } }
-                            .sortedBy { it.order }
-
-                    val summary = if (selectedCategories.isEmpty())
-                        getString(R.string.all)
-                    else
-                        selectedCategories.joinToString { it.name }
-
-                    categoryUpdate.summary = summary
-                }
-
-        defaultCategory.apply {
-            val selectedCategory = dbCategories.find { it.id == preferences.defaultCategory()}
-            value = selectedCategory?.id?.toString() ?: value
-            entries += dbCategories.map { it.name }.toTypedArray()
-            entryValues += dbCategories.map { it.id.toString() }.toTypedArray()
-            summary = selectedCategory?.name ?: summary
-        }
-
-        defaultCategory.setOnPreferenceChangeListener { _, newValue ->
-            defaultCategory.summary = dbCategories.find {
-                it.id == (newValue as String).toInt()
-            }?.name ?: getString(R.string.default_category_summary)
-
-            true
-        }
-
-        themePreference.setOnPreferenceChangeListener { preference, newValue ->
-            (activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_THEME_CHANGED
-            activity.recreate()
-            true
-        }
-
-        val langValues = langPreference.entryValues.map { value ->
-            val locale = LocaleHelper.getLocaleFromString(value.toString())
-            locale?.getDisplayName(locale)?.capitalize() ?: context.getString(R.string.system_default)
-        }
-
-        langPreference.entries = langValues.toTypedArray()
-        langPreference.setOnPreferenceChangeListener { preference, newValue ->
-            (activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_LANG_CHANGED
-            LocaleHelper.changeLocale(newValue.toString())
-            val app = activity.application
-            LocaleHelper.updateConfiguration(app, app.resources.configuration)
-            activity.recreate()
-            true
-        }
-
-    }
-
-    override fun onPreferenceDisplayDialog(p0: PreferenceFragmentCompat?, p: Preference): Boolean {
-        if (p === columnsPreference) {
-            val fragment = LibraryColumnsDialog.newInstance(p)
-            fragment.setTargetFragment(this, 0)
-            fragment.show(fragmentManager, null)
-            return true
-        }
-        return false
-    }
-
-    private fun updateColumnsSummary(portraitColumns: Int, landscapeColumns: Int) {
-        val portrait = getColumnValue(portraitColumns)
-        val landscape = getColumnValue(landscapeColumns)
-        val msg = "${getString(R.string.portrait)}: $portrait, ${getString(R.string.landscape)}: $landscape"
-
-        columnsPreference.summary = msg
-    }
-
-    private fun getColumnValue(value: Int): String {
-        return if (value == 0) getString(R.string.default_columns) else value.toString()
-    }
-
-}

+ 70 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt

@@ -0,0 +1,70 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.support.v7.preference.PreferenceScreen
+import com.bluelinelabs.conductor.RouterTransaction
+import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.util.getResourceColor
+
+class SettingsMainController : SettingsController() {
+    override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
+        titleRes = R.string.label_settings
+
+        val tintColor = context.getResourceColor(R.attr.colorAccent)
+
+        preference {
+            iconRes = R.drawable.ic_tune_black_24dp
+            iconTint = tintColor
+            titleRes = R.string.pref_category_general
+            onClick { navigateTo(SettingsGeneralController()) }
+        }
+        preference {
+            iconRes = R.drawable.ic_chrome_reader_mode_black_24dp
+            iconTint = tintColor
+            titleRes = R.string.pref_category_reader
+            onClick { navigateTo(SettingsReaderController()) }
+        }
+        preference {
+            iconRes = R.drawable.ic_file_download_black_24dp
+            iconTint = tintColor
+            titleRes = R.string.pref_category_downloads
+            onClick { navigateTo(SettingsDownloadController()) }
+        }
+        preference {
+            iconRes = R.drawable.ic_language_black_24dp
+            iconTint = tintColor
+            titleRes = R.string.pref_category_sources
+            onClick { navigateTo(SettingsSourcesController()) }
+        }
+        preference {
+            iconRes = R.drawable.ic_sync_black_24dp
+            iconTint = tintColor
+            titleRes = R.string.pref_category_tracking
+            onClick { navigateTo(SettingsTrackingController()) }
+        }
+        preference {
+            iconRes = R.drawable.ic_backup_black_24dp
+            iconTint = tintColor
+            titleRes = R.string.backup
+            onClick { navigateTo(SettingsBackupController()) }
+        }
+        preference {
+            iconRes = R.drawable.ic_code_black_24dp
+            iconTint = tintColor
+            titleRes = R.string.pref_category_advanced
+            onClick { navigateTo(SettingsAdvancedController()) }
+        }
+        preference {
+            iconRes = R.drawable.ic_help_black_24dp
+            iconTint = tintColor
+            titleRes = R.string.pref_category_about
+            onClick { navigateTo(SettingsAboutController()) }
+        }
+    }
+
+    private fun navigateTo(controller: SettingsController) {
+        router.pushController(RouterTransaction.with(controller)
+                .pushChangeHandler(FadeChangeHandler())
+                .popChangeHandler(FadeChangeHandler()))
+    }
+}

+ 106 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt

@@ -0,0 +1,106 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.support.v7.preference.PreferenceScreen
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
+
+class SettingsReaderController : SettingsController() {
+
+    override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
+        titleRes = R.string.pref_category_reader
+
+        intListPreference {
+            key = Keys.defaultViewer
+            titleRes = R.string.pref_viewer_type
+            entriesRes = arrayOf(R.string.left_to_right_viewer, R.string.right_to_left_viewer,
+                    R.string.vertical_viewer, R.string.webtoon_viewer)
+            entryValues = arrayOf("1", "2", "3", "4")
+            defaultValue = "1"
+            summary = "%s"
+        }
+        intListPreference {
+            key = Keys.imageScaleType
+            titleRes = R.string.pref_image_scale_type
+            entriesRes = arrayOf(R.string.scale_type_fit_screen, R.string.scale_type_stretch,
+                    R.string.scale_type_fit_width, R.string.scale_type_fit_height,
+                    R.string.scale_type_original_size, R.string.scale_type_smart_fit)
+            entryValues = arrayOf("1", "2", "3", "4", "5", "6")
+            defaultValue = "1"
+            summary = "%s"
+        }
+        intListPreference {
+            key = Keys.zoomStart
+            titleRes = R.string.pref_zoom_start
+            entriesRes = arrayOf(R.string.zoom_start_automatic, R.string.zoom_start_left,
+                    R.string.zoom_start_right, R.string.zoom_start_center)
+            entryValues = arrayOf("1", "2", "3", "4")
+            defaultValue = "1"
+            summary = "%s"
+        }
+        intListPreference {
+            key = Keys.rotation
+            titleRes = R.string.pref_rotation_type
+            entriesRes = arrayOf(R.string.rotation_free, R.string.rotation_lock,
+                    R.string.rotation_force_portrait, R.string.rotation_force_landscape)
+            entryValues = arrayOf("1", "2", "3", "4")
+            defaultValue = "1"
+            summary = "%s"
+        }
+        intListPreference {
+            key = Keys.readerTheme
+            titleRes = R.string.pref_reader_theme
+            entriesRes = arrayOf(R.string.white_background, R.string.black_background)
+            entryValues = arrayOf("0", "1")
+            defaultValue = "0"
+            summary = "%s"
+        }
+        intListPreference {
+            key = Keys.imageDecoder
+            titleRes = R.string.pref_image_decoder
+            entries = arrayOf("Image", "Rapid", "Skia")
+            entryValues = arrayOf("0", "1", "2")
+            defaultValue = "0"
+            summary = "%s"
+        }
+        switchPreference {
+            key = Keys.fullscreen
+            titleRes = R.string.pref_fullscreen
+            defaultValue = true
+        }
+        switchPreference {
+            key = Keys.enableTransitions
+            titleRes = R.string.pref_page_transitions
+            defaultValue = true
+        }
+        switchPreference {
+            key = Keys.showPageNumber
+            titleRes = R.string.pref_show_page_number
+            defaultValue = true
+        }
+        switchPreference {
+            key = Keys.cropBorders
+            titleRes = R.string.pref_crop_borders
+            defaultValue = false
+        }
+        switchPreference {
+            key = Keys.keepScreenOn
+            titleRes = R.string.pref_keep_screen_on
+            defaultValue = true
+        }
+        preferenceCategory {
+            titleRes = R.string.pref_reader_navigation
+
+            switchPreference {
+                key = Keys.readWithTapping
+                titleRes = R.string.pref_read_with_tapping
+                defaultValue = true
+            }
+            switchPreference {
+                key = Keys.readWithVolumeKeys
+                titleRes = R.string.pref_read_with_volume_keys
+                defaultValue = false
+            }
+        }
+    }
+
+}

+ 25 - 45
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt → app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt

@@ -1,47 +1,27 @@
 package eu.kanade.tachiyomi.ui.setting
 
-import android.content.Intent
 import android.graphics.drawable.Drawable
-import android.os.Bundle
-import android.support.v7.preference.XpPreferenceFragment
-import android.view.View
-import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import android.support.v7.preference.PreferenceGroup
+import android.support.v7.preference.PreferenceScreen
+import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.preference.getOrDefault
 import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.source.online.HttpSource
+import eu.kanade.tachiyomi.source.online.LoginSource
 import eu.kanade.tachiyomi.widget.preference.LoginCheckBoxPreference
 import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog
 import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
-import uy.kohesive.injekt.injectLazy
 import java.util.*
 
-class SettingsSourcesFragment : SettingsFragment() {
-
-    companion object {
-        const val SOURCE_CHANGE_REQUEST = 120
-
-        fun newInstance(rootKey: String?): SettingsSourcesFragment {
-            val args = Bundle()
-            args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
-            return SettingsSourcesFragment().apply { arguments = args }
-        }
-    }
-
-    private val preferences: PreferencesHelper by injectLazy()
+class SettingsSourcesController : SettingsController(),
+        SourceLoginDialog.Listener {
 
     private val onlineSources by lazy { Injekt.get<SourceManager>().getOnlineSources() }
 
-    override fun setDivider(divider: Drawable?) {
-        super.setDivider(null)
-    }
-
-    override fun onViewCreated(view: View, savedState: Bundle?) {
-        super.onViewCreated(view, savedState)
-
-        // Remove dummy preference
-        preferenceScreen.removeAll()
+    override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
+        titleRes = R.string.pref_category_sources
 
         // Get the list of active language codes.
         val activeLangsCodes = preferences.enabledLanguages().getOrDefault()
@@ -66,8 +46,8 @@ class SettingsSourcesFragment : SettingsFragment() {
                     addLanguageSources(this, sources)
                 }
 
-                setOnPreferenceChangeListener { preference, any ->
-                    val checked = any as Boolean
+                onChange { newValue ->
+                    val checked = newValue as Boolean
                     val current = preferences.enabledLanguages().getOrDefault()
                     if (!checked) {
                         preferences.enabledLanguages().set(current - lang)
@@ -82,24 +62,28 @@ class SettingsSourcesFragment : SettingsFragment() {
         }
     }
 
+    override fun setDivider(divider: Drawable?) {
+        super.setDivider(null)
+    }
+
     /**
      * Adds the source list for the given group (language).
      *
      * @param group the language category.
      */
-    private fun addLanguageSources(group: SwitchPreferenceCategory, sources: List<HttpSource>) {
+    private fun addLanguageSources(group: PreferenceGroup, sources: List<HttpSource>) {
         val hiddenCatalogues = preferences.hiddenCatalogues().getOrDefault()
 
         sources.forEach { source ->
-            val sourcePreference = LoginCheckBoxPreference(context, source).apply {
+            val sourcePreference = LoginCheckBoxPreference(group.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
+                onChange { newValue ->
+                    val checked = newValue as Boolean
                     val current = preferences.hiddenCatalogues().getOrDefault()
 
                     preferences.hiddenCatalogues().set(if (checked)
@@ -111,27 +95,23 @@ class SettingsSourcesFragment : SettingsFragment() {
                 }
 
                 setOnLoginClickListener {
-                    val fragment = SourceLoginDialog.newInstance(source)
-                    fragment.setTargetFragment(this@SettingsSourcesFragment, SOURCE_CHANGE_REQUEST)
-                    fragment.show(fragmentManager, null)
+                    val dialog = SourceLoginDialog(source)
+                    dialog.targetController = this@SettingsSourcesController
+                    dialog.showDialog(router)
                 }
-
             }
 
             group.addPreference(sourcePreference)
         }
     }
 
-    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        if (requestCode == SOURCE_CHANGE_REQUEST && data != null) {
-            val sourceId = data.getLongExtra("key", -1L)
-            val pref = findPreference(getSourceKey(sourceId)) as? LoginCheckBoxPreference
-            pref?.notifyChanged()
-        }
+    override fun loginDialogClosed(source: LoginSource) {
+        val pref = findPreference(getSourceKey(source.id)) as? LoginCheckBoxPreference
+        pref?.notifyChanged()
     }
 
     private fun getSourceKey(sourceId: Long): String {
         return "source_$sourceId"
     }
 
-}
+}

+ 91 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt

@@ -0,0 +1,91 @@
+package eu.kanade.tachiyomi.ui.setting
+
+import android.app.Activity
+import android.content.Intent
+import android.support.customtabs.CustomTabsIntent
+import android.support.v7.preference.PreferenceScreen
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.track.TrackManager
+import eu.kanade.tachiyomi.data.track.TrackService
+import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
+import eu.kanade.tachiyomi.util.getResourceColor
+import eu.kanade.tachiyomi.widget.preference.LoginPreference
+import eu.kanade.tachiyomi.widget.preference.TrackLoginDialog
+import uy.kohesive.injekt.injectLazy
+import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
+
+class SettingsTrackingController : SettingsController(),
+        TrackLoginDialog.Listener {
+
+    private val trackManager: TrackManager by injectLazy()
+
+    override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) {
+        titleRes = R.string.pref_category_tracking
+
+        switchPreference {
+            key = Keys.autoUpdateTrack
+            titleRes = R.string.pref_auto_update_manga_sync
+            defaultValue = true
+        }
+        switchPreference {
+            key = Keys.askUpdateTrack
+            titleRes = R.string.pref_ask_update_manga_sync
+            defaultValue = false
+        }.apply {
+            dependency = Keys.autoUpdateTrack // the preference needs to be attached.
+        }
+        preferenceCategory {
+            titleRes = R.string.services
+
+            trackPreference(trackManager.myAnimeList) {
+                onClick {
+                    val dialog = TrackLoginDialog(trackManager.myAnimeList)
+                    dialog.targetController = this@SettingsTrackingController
+                    dialog.showDialog(router)
+                }
+            }
+            trackPreference(trackManager.aniList) {
+                onClick {
+                    val tabsIntent = CustomTabsIntent.Builder()
+                            .setToolbarColor(context.getResourceColor(R.attr.colorPrimary))
+                            .build()
+                    tabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
+                    tabsIntent.launchUrl(activity, AnilistApi.authUrl())
+                }
+            }
+            trackPreference(trackManager.kitsu) {
+                onClick {
+                    val dialog = TrackLoginDialog(trackManager.kitsu)
+                    dialog.targetController = this@SettingsTrackingController
+                    dialog.showDialog(router)
+                }
+            }
+        }
+    }
+
+    inline fun PreferenceScreen.trackPreference(
+            service: TrackService,
+            block: (@DSL LoginPreference).() -> Unit
+    ): LoginPreference {
+        return initThenAdd(LoginPreference(context).apply {
+            key = Keys.trackUsername(service.id)
+            title = service.name
+        }, block)
+    }
+
+    override fun onActivityResumed(activity: Activity) {
+        super.onActivityResumed(activity)
+        // Manually refresh anilist holder
+        updatePreference(trackManager.aniList.id)
+    }
+
+    private fun updatePreference(id: Int) {
+        val pref = findPreference(Keys.trackUsername(id)) as? LoginPreference
+        pref?.notifyChanged()
+    }
+
+    override fun trackDialogClosed(service: TrackService) {
+        updatePreference(service.id)
+    }
+
+}

+ 0 - 95
app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingFragment.kt

@@ -1,95 +0,0 @@
-package eu.kanade.tachiyomi.ui.setting
-
-import android.content.Intent
-import android.os.Bundle
-import android.support.customtabs.CustomTabsIntent
-import android.support.v7.preference.PreferenceCategory
-import android.support.v7.preference.XpPreferenceFragment
-import android.view.View
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.preference.PreferencesHelper
-import eu.kanade.tachiyomi.data.track.TrackManager
-import eu.kanade.tachiyomi.data.track.TrackService
-import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
-import eu.kanade.tachiyomi.util.getResourceColor
-import eu.kanade.tachiyomi.widget.preference.LoginPreference
-import eu.kanade.tachiyomi.widget.preference.TrackLoginDialog
-import uy.kohesive.injekt.injectLazy
-
-class SettingsTrackingFragment : SettingsFragment() {
-
-    companion object {
-        const val SYNC_CHANGE_REQUEST = 121
-
-        fun newInstance(rootKey: String): SettingsTrackingFragment {
-            val args = Bundle()
-            args.putString(XpPreferenceFragment.ARG_PREFERENCE_ROOT, rootKey)
-            return SettingsTrackingFragment().apply { arguments = args }
-        }
-    }
-
-    private val trackManager: TrackManager by injectLazy()
-
-    private val preferences: PreferencesHelper by injectLazy()
-
-    val syncCategory: PreferenceCategory by bindPref(R.string.pref_category_tracking_accounts_key)
-
-    override fun onViewCreated(view: View, savedState: Bundle?) {
-        super.onViewCreated(view, savedState)
-
-        registerService(trackManager.myAnimeList)
-
-        registerService(trackManager.aniList) {
-            val intent = CustomTabsIntent.Builder()
-                    .setToolbarColor(activity.getResourceColor(R.attr.colorPrimary))
-                    .build()
-            intent.intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
-            intent.launchUrl(activity, AnilistApi.authUrl())
-        }
-
-        registerService(trackManager.kitsu)
-    }
-
-    private fun <T : TrackService> registerService(
-            service: T,
-            onPreferenceClick: (T) -> Unit = defaultOnPreferenceClick) {
-
-        LoginPreference(preferenceManager.context).apply {
-            key = preferences.keys.trackUsername(service.id)
-            title = service.name
-
-            setOnPreferenceClickListener {
-                onPreferenceClick(service)
-                true
-            }
-
-            syncCategory.addPreference(this)
-        }
-    }
-
-    private val defaultOnPreferenceClick: (TrackService) -> Unit
-        get() = {
-            val fragment = TrackLoginDialog.newInstance(it)
-            fragment.setTargetFragment(this, SYNC_CHANGE_REQUEST)
-            fragment.show(fragmentManager, null)
-        }
-
-    override fun onResume() {
-        super.onResume()
-        // Manually refresh anilist holder
-        updatePreference(trackManager.aniList.id)
-    }
-
-    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        if (requestCode == SYNC_CHANGE_REQUEST && data != null) {
-            val serviceId = data.getIntExtra("key", -1)
-            updatePreference(serviceId)
-        }
-    }
-
-    private fun updatePreference(id: Int) {
-        val pref = findPreference(preferences.keys.trackUsername(id)) as? LoginPreference
-        pref?.notifyChanged()
-    }
-
-}

+ 8 - 1
app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt

@@ -107,13 +107,20 @@ object DiskUtil {
      * Scans the given file so that it can be shown in gallery apps, for example.
      */
     fun scanMedia(context: Context, file: File) {
+        scanMedia(context, Uri.fromFile(file))
+    }
+
+    /**
+     * Scans the given file so that it can be shown in gallery apps, for example.
+     */
+    fun scanMedia(context: Context, uri: Uri) {
         val action = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
             Intent.ACTION_MEDIA_MOUNTED
         } else {
             Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
         }
         val mediaScanIntent = Intent(action)
-        mediaScanIntent.data = Uri.fromFile(file)
+        mediaScanIntent.data = uri
         context.sendBroadcast(mediaScanIntent)
     }
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt

@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.widget.preference
 
 import android.content.Context
 import android.graphics.Color
+import android.support.v7.preference.CheckBoxPreference
 import android.support.v7.preference.PreferenceViewHolder
 import android.util.AttributeSet
 import android.view.View
@@ -11,7 +12,6 @@ import eu.kanade.tachiyomi.source.online.LoginSource
 import eu.kanade.tachiyomi.util.getResourceColor
 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,

+ 14 - 15
app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt

@@ -1,23 +1,22 @@
 package eu.kanade.tachiyomi.widget.preference
 
-import android.app.Activity
 import android.app.Dialog
-import android.content.DialogInterface
-import android.content.Intent
 import android.os.Bundle
-import android.support.v4.app.DialogFragment
 import android.text.method.PasswordTransformationMethod
 import android.view.View
 import com.afollestad.materialdialogs.MaterialDialog
+import com.bluelinelabs.conductor.ControllerChangeHandler
+import com.bluelinelabs.conductor.ControllerChangeType
 import com.dd.processbutton.iml.ActionProcessButton
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.ui.base.controller.DialogController
 import eu.kanade.tachiyomi.widget.SimpleTextWatcher
 import kotlinx.android.synthetic.main.pref_account_login.view.*
 import rx.Subscription
 import uy.kohesive.injekt.injectLazy
 
-abstract class LoginDialogPreference : DialogFragment() {
+abstract class LoginDialogPreference(bundle: Bundle? = null) : DialogController(bundle) {
 
     var v: View? = null
         private set
@@ -27,7 +26,7 @@ abstract class LoginDialogPreference : DialogFragment() {
     var requestSubscription: Subscription? = null
 
     override fun onCreateDialog(savedState: Bundle?): Dialog {
-        val dialog = MaterialDialog.Builder(activity)
+        val dialog = MaterialDialog.Builder(activity!!)
                 .customView(R.layout.pref_account_login, false)
                 .negativeText(android.R.string.cancel)
                 .build()
@@ -37,7 +36,7 @@ abstract class LoginDialogPreference : DialogFragment() {
         return dialog
     }
 
-    override fun onViewCreated(view: View, savedState: Bundle?) {
+    fun onViewCreated(view: View, savedState: Bundle?) {
         v = view.apply {
             show_password.setOnCheckedChangeListener { v, isChecked ->
                 if (isChecked)
@@ -55,7 +54,7 @@ abstract class LoginDialogPreference : DialogFragment() {
 
             password.addTextChangedListener(object : SimpleTextWatcher() {
                 override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
-                    if (s.length == 0) {
+                    if (s.isEmpty()) {
                         show_password.isEnabled = true
                     }
                 }
@@ -64,15 +63,15 @@ abstract class LoginDialogPreference : DialogFragment() {
 
     }
 
-    override fun onPause() {
-        super.onPause()
-        requestSubscription?.unsubscribe()
+    override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
+        super.onChangeStarted(handler, type)
+        if (!type.isEnter) {
+            onDialogClosed()
+        }
     }
 
-    override fun onDismiss(dialog: DialogInterface) {
-        super.onDismiss(dialog)
-        val intent = Intent().putExtras(arguments)
-        targetFragment?.onActivityResult(targetRequestCode, Activity.RESULT_OK, intent)
+    open fun onDialogClosed() {
+        requestSubscription?.unsubscribe()
     }
 
     protected abstract fun checkLogin()

+ 16 - 24
app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt

@@ -10,34 +10,17 @@ import eu.kanade.tachiyomi.util.toast
 import kotlinx.android.synthetic.main.pref_account_login.view.*
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
-import uy.kohesive.injekt.injectLazy
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
 
-class SourceLoginDialog : LoginDialogPreference() {
+class SourceLoginDialog(bundle: Bundle? = null) : LoginDialogPreference(bundle) {
 
-    companion object {
+    private val source = Injekt.get<SourceManager>().get(args.getLong("key")) as LoginSource
 
-        fun newInstance(source: Source): LoginDialogPreference {
-            val fragment = SourceLoginDialog()
-            val bundle = Bundle(1)
-            bundle.putLong("key", source.id)
-            fragment.arguments = bundle
-            return fragment
-        }
-    }
-
-    val sourceManager: SourceManager by injectLazy()
-
-    lateinit var source: LoginSource
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        val sourceId = arguments.getLong("key")
-        source = sourceManager.get(sourceId) as LoginSource
-    }
+    constructor(source: Source) : this(Bundle().apply { putLong("key", source.id) })
 
     override fun setCredentialsOnView(view: View) = with(view) {
-        dialog_title.text = getString(R.string.login_title, source.toString())
+        dialog_title.text = context.getString(R.string.login_title, source.toString())
         username.setText(preferences.sourceUsername(source))
         password.setText(preferences.sourcePassword(source))
     }
@@ -60,7 +43,7 @@ class SourceLoginDialog : LoginDialogPreference() {
                                     username.text.toString(),
                                     password.text.toString())
 
-                            dialog.dismiss()
+                            dialog?.dismiss()
                             context.toast(R.string.login_success)
                         } else {
                             preferences.setSourceCredentials(source, "", "")
@@ -74,4 +57,13 @@ class SourceLoginDialog : LoginDialogPreference() {
         }
     }
 
+    override fun onDialogClosed() {
+        super.onDialogClosed()
+        (targetController as? Listener)?.loginDialogClosed(source)
+    }
+
+    interface Listener {
+        fun loginDialogClosed(source: LoginSource)
+    }
+
 }

+ 6 - 8
app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt

@@ -4,15 +4,16 @@ import android.annotation.TargetApi
 import android.content.Context
 import android.content.res.TypedArray
 import android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH
+import android.support.v7.preference.PreferenceCategory
 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.TextView
+import eu.kanade.tachiyomi.R
 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,
@@ -20,20 +21,17 @@ class SwitchPreferenceCategory @JvmOverloads constructor(
 : PreferenceCategory(
         context,
         attrs,
-        R.attr.switchPreferenceCompatStyle,
-        R.style.Preference_Material_SwitchPreferenceCompat),
+        R.attr.switchPreferenceCompatStyle),
 CompoundButton.OnCheckedChangeListener {
 
-    init {
-        setTitleTextColor(context.getResourceColor(R.attr.colorAccent))
-    }
-
     private var mChecked = false
 
     private var mCheckedSet = false
 
     override fun onBindViewHolder(holder: PreferenceViewHolder) {
         super.onBindViewHolder(holder)
+        val titleView = holder.findViewById(android.R.id.title) as TextView
+        titleView.setTextColor(context.getResourceColor(R.attr.colorAccent))
         syncSwitchView(holder)
     }
 

+ 19 - 27
app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt

@@ -9,36 +9,19 @@ import eu.kanade.tachiyomi.util.toast
 import kotlinx.android.synthetic.main.pref_account_login.view.*
 import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
-import uy.kohesive.injekt.injectLazy
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
 
-class TrackLoginDialog : LoginDialogPreference() {
+class TrackLoginDialog(bundle: Bundle? = null) : LoginDialogPreference(bundle) {
 
-    companion object {
+    private val service = Injekt.get<TrackManager>().getService(args.getInt("key"))!!
 
-        fun newInstance(sync: TrackService): LoginDialogPreference {
-            val fragment = TrackLoginDialog()
-            val bundle = Bundle(1)
-            bundle.putInt("key", sync.id)
-            fragment.arguments = bundle
-            return fragment
-        }
-    }
-
-    val trackManager: TrackManager by injectLazy()
-
-    lateinit var sync: TrackService
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        val syncId = arguments.getInt("key")
-        sync = trackManager.getService(syncId)!!
-    }
+    constructor(service: TrackService) : this(Bundle().apply { putInt("key", service.id) })
 
     override fun setCredentialsOnView(view: View) = with(view) {
-        dialog_title.text = getString(R.string.login_title, sync.name)
-        username.setText(sync.getUsername())
-        password.setText(sync.getPassword())
+        dialog_title.text = context.getString(R.string.login_title, service.name)
+        username.setText(service.getUsername())
+        password.setText(service.getPassword())
     }
 
     override fun checkLogin() {
@@ -52,11 +35,11 @@ class TrackLoginDialog : LoginDialogPreference() {
             val user = username.text.toString()
             val pass = password.text.toString()
 
-            requestSubscription = sync.login(user, pass)
+            requestSubscription = service.login(user, pass)
                     .subscribeOn(Schedulers.io())
                     .observeOn(AndroidSchedulers.mainThread())
                     .subscribe({
-                        dialog.dismiss()
+                        dialog?.dismiss()
                         context.toast(R.string.login_success)
                     }, { error ->
                         login.progress = -1
@@ -67,4 +50,13 @@ class TrackLoginDialog : LoginDialogPreference() {
         }
     }
 
+    override fun onDialogClosed() {
+        super.onDialogClosed()
+        (targetController as? Listener)?.trackDialogClosed(service)
+    }
+
+    interface Listener {
+        fun trackDialogClosed(service: TrackService)
+    }
+
 }

+ 2 - 0
app/src/main/res/layout/pref_library_columns.xml

@@ -25,6 +25,7 @@
             android:id="@+id/portrait_columns"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:descendantFocusability="blocksDescendants"
             app:max="10"
             app:min="0"/>
 
@@ -46,6 +47,7 @@
             android:id="@+id/landscape_columns"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:descendantFocusability="blocksDescendants"
             app:max="10"
             app:min="0"/>
 

+ 0 - 76
app/src/main/res/values/keys.xml

@@ -1,81 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <string name="pref_category_general_key" translatable="false">pref_category_general_key</string>
-    <string name="pref_category_reader_key" translatable="false">pref_category_reader_key</string>
-    <string name="pref_category_tracking_key" translatable="false">pref_category_tracking_key</string>
-    <string name="pref_category_downloads_key" translatable="false">pref_category_downloads_key</string>
-    <string name="pref_category_advanced_key" translatable="false">pref_category_advanced_key</string>
-    <string name="pref_category_about_key" translatable="false">pref_category_about_key</string>
-    <string name="pref_category_sources_key" translatable="false">pref_category_sources_key</string>
-
-    <string name="pref_display_library_as_list" translatable="false">pref_display_library_as_list</string>
-    <string name="pref_library_columns_dialog_key" translatable="false">pref_library_columns_dialog_key</string>
-    <string name="pref_library_columns_portrait_key" translatable="false">pref_library_columns_portrait_key</string>
-    <string name="pref_library_columns_landscape_key" translatable="false">pref_library_columns_landscape_key</string>
-    <string name="pref_library_update_interval_key" translatable="false">pref_library_update_interval_key</string>
-    <string name="pref_library_update_categories_key" translatable="false">library_update_categories</string>
-    <string name="pref_update_only_non_completed_key" translatable="false">pref_update_only_non_completed_key</string>
-    <string name="pref_auto_update_manga_sync_key" translatable="false">pref_auto_update_manga_sync_key</string>
-    <string name="pref_ask_update_manga_sync_key" translatable="false">pref_ask_update_manga_sync_key</string>
-    <string name="pref_theme_key" translatable="false">pref_theme_key</string>
-    <string name="pref_library_update_restriction_key" translatable="false">library_update_restriction</string>
-    <string name="pref_start_screen_key" translatable="false">start_screen</string>
-    <string name="pref_language_key" translatable="false">app_language</string>
-    <string name="default_category_key" translatable="false">default_category</string>
-
-    <string name="pref_default_viewer_key" translatable="false">pref_default_viewer_key</string>
-    <string name="pref_image_scale_type_key" translatable="false">pref_image_scale_type_key</string>
-    <string name="pref_zoom_start_key" translatable="false">pref_zoom_start_key</string>
-    <string name="pref_fullscreen_key" translatable="false">fullscreen</string>
-    <string name="pref_rotation_type_key" translatable="false">pref_rotation_type_key</string>
-    <string name="pref_enable_transitions_key" translatable="false">pref_enable_transitions_key</string>
-    <string name="pref_show_page_number_key" translatable="false">pref_show_page_number_key</string>
-    <string name="pref_keep_screen_on_key" translatable="false">pref_keep_screen_on_key</string>
-    <string name="pref_custom_brightness_key" translatable="false">pref_custom_brightness_key</string>
-    <string name="pref_custom_brightness_value_key" translatable="false">custom_brightness_value</string>
-    <string name="pref_color_filter_key" translatable="false">pref_color_filter_key</string>
-    <string name="pref_color_filter_value_key" translatable="false">color_filter_value</string>
-    <string name="pref_red_filter_value_key" translatable="false">pref_red_filter_value</string>
-    <string name="pref_reader_theme_key" translatable="false">pref_reader_theme_key</string>
-    <string name="pref_image_decoder_key" translatable="false">image_decoder</string>
-    <string name="pref_crop_borders_key" translatable="false">crop_borders</string>
-    <string name="pref_read_with_volume_keys_key" translatable="false">reader_volume_keys</string>
-    <string name="pref_read_with_tapping_key" translatable="false">reader_tap</string>
-
-    <string name="pref_filter_downloaded_key" translatable="false">pref_filter_downloaded_key</string>
-    <string name="pref_filter_unread_key" translatable="false">pref_filter_unread_key</string>
-    <string name="pref_library_sorting_mode_key" translatable="false">library_sorting_mode</string>
-
-    <string name="pref_download_directory_key" translatable="false">download_directory</string>
-    <string name="pref_download_slots_key" translatable="false">pref_download_slots_key</string>
-    <string name="pref_remove_after_read_slots_key" translatable="false">remove_after_read_slots</string>
-    <string name="pref_download_only_over_wifi_key" translatable="false">pref_download_only_over_wifi_key</string>
-    <string name="pref_remove_after_marked_as_read_key" translatable="false">pref_remove_after_marked_as_read_key</string>
-    <string name="pref_last_used_category_key" translatable="false">last_used_category</string>
-
-    <string name="pref_create_local_backup_key" translatable="false">create_local_backup</string>
-    <string name="pref_restore_local_backup_key" translatable="false">restore_local_backup</string>
-    <string name="pref_backup_interval_key" translatable="false">backup_interval</string>
-    <string name="pref_backup_directory_key" translatable="false">backup_directory</string>
-    <string name="pref_backup_slots_key" translatable="false">backup_slots</string>
-
-    <string name="pref_source_languages" translatable="false">source_languages</string>
-    <string name="pref_category_tracking_accounts_key" translatable="false">category_tracking_accounts</string>
-
-    <string name="pref_clear_chapter_cache_key" translatable="false">pref_clear_chapter_cache_key</string>
-    <string name="pref_clear_database_key" translatable="false">pref_clear_database_key</string>
-    <string name="pref_clear_cookies_key" translatable="false">pref_clear_cookies_key</string>
-    <string name="pref_refresh_library_metadata_key" translatable="false">refresh_library_metadata</string>
-
-    <string name="pref_version" translatable="false">pref_version</string>
-    <string name="pref_build_time" translatable="false">pref_build_time</string>
-    <string name="pref_enable_automatic_updates_key" translatable="false">automatic_updates</string>
-
-    <string name="pref_display_catalogue_as_list" translatable="false">pref_display_catalogue_as_list</string>
-    <string name="pref_last_catalogue_source_key" translatable="false">last_catalogue_source</string>
-
-    <string name="pref_download_new_key" translatable="false">download_new</string>
-    <string name="pref_download_new_categories_key" translatable="false">download_new_categories</string>
 
     <!-- String Fonts -->
     <string name="font_roboto_medium" translatable="false">sans-serif</string>

+ 0 - 4
app/src/main/res/values/themes.xml

@@ -36,8 +36,6 @@
         <item name="selectable_library_drawable">@drawable/library_item_selector_light</item>
         <item name="text_color_primary">@color/textColorPrimaryLight</item>
         <item name="background_card">@color/dialogLight</item>
-        <item name="asp_preferenceIconTint">?colorAccent</item>
-        <item name="asp_preferenceDialogIconTint">?colorAccent</item>
     </style>
 
     <style name="Theme.Tachiyomi" parent="Theme.Base">
@@ -76,8 +74,6 @@
         <item name="selectable_library_drawable">@drawable/library_item_selector_dark</item>
         <item name="text_color_primary">@color/textColorPrimaryDark</item>
         <item name="background_card">@color/dialogDark</item>
-        <item name="asp_preferenceIconTint">?colorAccent</item>
-        <item name="asp_preferenceDialogIconTint">?colorAccent</item>
     </style>
 
     <style name="Theme.Tachiyomi.Dark" parent="Theme.Base.Dark">

+ 0 - 37
app/src/main/res/xml/pref_about.xml

@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-
-    <PreferenceScreen
-        android:icon="@drawable/ic_help_black_24dp"
-        android:key="about_screen"
-        android:persistent="false"
-        android:title="@string/pref_category_about"
-        app:asp_tintEnabled="true">
-
-        <SwitchPreference
-            android:defaultValue="true"
-            android:key="acra.enable"
-            android:summary="@string/pref_acra_summary"
-            android:title="@string/pref_enable_acra" />
-
-        <SwitchPreference
-            android:defaultValue="false"
-            android:key="@string/pref_enable_automatic_updates_key"
-            android:summary="@string/pref_enable_automatic_updates_summary"
-            android:title="@string/pref_enable_automatic_updates" />
-
-        <Preference
-            android:key="@string/pref_version"
-            android:persistent="false"
-            android:title="@string/version"/>
-
-        <Preference
-            android:key="@string/pref_build_time"
-            android:persistent="false"
-            android:title="@string/build_time"/>
-
-    </PreferenceScreen>
-
-</PreferenceScreen>

+ 0 - 33
app/src/main/res/xml/pref_advanced.xml

@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-
-    <PreferenceScreen
-        android:icon="@drawable/ic_code_black_24dp"
-        android:key="advanced_screen"
-        android:persistent="false"
-        android:title="@string/pref_category_advanced"
-        app:asp_tintEnabled="true">
-
-        <Preference
-            android:key="@string/pref_clear_chapter_cache_key"
-            android:title="@string/pref_clear_chapter_cache"/>
-
-        <Preference
-            android:key="@string/pref_clear_cookies_key"
-            android:title="@string/pref_clear_cookies"/>
-
-        <Preference
-            android:key="@string/pref_clear_database_key"
-            android:summary="@string/pref_clear_database_summary"
-            android:title="@string/pref_clear_database"/>
-
-        <Preference
-            android:key="@string/pref_refresh_library_metadata_key"
-            android:summary="@string/pref_refresh_library_metadata_summary"
-            android:title="@string/pref_refresh_library_metadata"/>
-
-    </PreferenceScreen>
-
-</PreferenceScreen>

+ 0 - 48
app/src/main/res/xml/pref_backup.xml

@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-
-    <PreferenceScreen
-        android:icon="@drawable/ic_backup_black_24dp"
-        android:key="backup_screen"
-        android:persistent="false"
-        android:title="Backup"
-        app:asp_tintEnabled="true">
-
-        <Preference
-            android:key="@string/pref_create_local_backup_key"
-            android:summary="@string/pref_create_backup_summ"
-            android:title="@string/pref_create_backup" />
-
-        <Preference
-            android:key="@string/pref_restore_local_backup_key"
-            android:summary="@string/pref_restore_backup_summ"
-            android:title="@string/pref_restore_backup" />
-
-        <PreferenceCategory
-            android:persistent="false"
-            android:title="@string/pref_backup_service_category" />
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:defaultValue="0"
-            android:entries="@array/backup_update_interval"
-            android:entryValues="@array/backup_update_interval_values"
-            android:key="@string/pref_backup_interval_key"
-            android:summary="%s"
-            android:title="@string/pref_backup_interval"/>
-
-        <Preference
-            android:key="@string/pref_backup_directory_key"
-            android:title="@string/pref_backup_directory" />
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:defaultValue="1"
-            android:entries="@array/backup_slots"
-            android:entryValues="@array/backup_slots"
-            android:key="@string/pref_backup_slots_key"
-            android:summary="%s"
-            android:title="@string/pref_backup_slots" />
-
-    </PreferenceScreen>
-
-</PreferenceScreen>

+ 0 - 62
app/src/main/res/xml/pref_downloads.xml

@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-
-    <PreferenceScreen
-        android:icon="@drawable/ic_file_download_black_24dp"
-        android:key="downloads_screen"
-        android:persistent="false"
-        android:title="@string/pref_category_downloads"
-        app:asp_tintEnabled="true">
-
-        <Preference
-            android:key="@string/pref_download_directory_key"
-            android:title="@string/pref_download_directory"/>
-
-        <SwitchPreference
-            android:defaultValue="true"
-            android:key="@string/pref_download_only_over_wifi_key"
-            android:title="@string/pref_download_only_over_wifi" />
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:defaultValue="1"
-            android:entries="@array/download_slots"
-            android:entryValues="@array/download_slots"
-            android:key="@string/pref_download_slots_key"
-            android:summary="%s"
-            android:title="@string/pref_download_slots"/>
-
-        <PreferenceCategory
-            android:persistent="false"
-            android:title="@string/pref_remove_after_read" />
-
-        <SwitchPreference
-            android:defaultValue="false"
-            android:key="@string/pref_remove_after_marked_as_read_key"
-            android:title="@string/pref_remove_after_marked_as_read" />
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:defaultValue="-1"
-            android:entries="@array/remove_after_read_slots"
-            android:entryValues="@array/remove_after_read_slots_values"
-            android:key="@string/pref_remove_after_read_slots_key"
-            android:summary="%s"
-            android:title="@string/pref_remove_after_read" />
-
-        <PreferenceCategory
-            android:persistent="false"
-            android:title="@string/pref_download_new" />
-
-        <SwitchPreference
-            android:defaultValue="false"
-            android:key="@string/pref_download_new_key"
-            android:title="@string/pref_download_new"/>
-
-        <MultiSelectListPreference
-            android:key="@string/pref_download_new_categories_key"
-            android:title="@string/pref_download_new_categories" />
-
-    </PreferenceScreen>
-
-</PreferenceScreen>

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

@@ -1,76 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-
-    <PreferenceScreen
-        android:icon="@drawable/ic_tune_black_24dp"
-        android:key="general_screen"
-        android:persistent="false"
-        android:title="@string/pref_category_general"
-        app:asp_tintEnabled="true">
-
-        <ListPreference
-            android:defaultValue=""
-            android:entryValues="@array/languages_values"
-            android:key="@string/pref_language_key"
-            android:summary="%s"
-            android:title="@string/pref_language" />
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:defaultValue="1"
-            android:entries="@array/themes"
-            android:entryValues="@array/themes_values"
-            android:key="@string/pref_theme_key"
-            android:summary="%s"
-            android:title="@string/pref_theme"/>
-        
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-                android:title="@string/pref_start_screen"
-                android:key="@string/pref_start_screen_key"
-                android:entries="@array/start_screen_selection"
-                android:entryValues="@array/start_screen_selection_values"
-                android:defaultValue="1"
-                android:summary="%s"/>
-
-        <eu.kanade.tachiyomi.widget.preference.SimpleDialogPreference
-            android:dialogLayout="@layout/pref_library_columns"
-            android:key="@string/pref_library_columns_dialog_key"
-            android:persistent="false"
-            android:title="@string/pref_library_columns"/>
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:defaultValue="0"
-            android:entries="@array/library_update_interval"
-            android:entryValues="@array/library_update_interval_values"
-            android:key="@string/pref_library_update_interval_key"
-            android:summary="%s"
-            android:title="@string/pref_library_update_interval"/>
-
-        <MultiSelectListPreference
-            android:entries="@array/library_update_restrictions"
-            android:entryValues="@array/library_update_restrictions_values"
-            android:key="@string/pref_library_update_restriction_key"
-            android:summary="@string/pref_library_update_restriction_summary"
-            android:title="@string/pref_library_update_restriction" />
-
-        <MultiSelectListPreference
-            android:key="@string/pref_library_update_categories_key"
-            android:title="@string/pref_library_update_categories"/>
-
-        <SwitchPreference
-            android:defaultValue="false"
-            android:key="@string/pref_update_only_non_completed_key"
-            android:title="@string/pref_update_only_non_completed" />
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:defaultValue="-1"
-            android:entries="@array/default_category_entry"
-            android:entryValues="@array/default_category_entry_value"
-            android:key="@string/default_category_key"
-            android:title="@string/default_category"
-            android:summary="@string/default_category_summary"/>
-
-    </PreferenceScreen>
-
-</PreferenceScreen>

+ 0 - 103
app/src/main/res/xml/pref_reader.xml

@@ -1,103 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-
-    <PreferenceScreen
-        android:icon="@drawable/ic_chrome_reader_mode_black_24dp"
-        android:key="reader_screen"
-        android:persistent="false"
-        android:title="@string/pref_category_reader"
-        app:asp_tintEnabled="true">
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:title="@string/pref_viewer_type"
-            android:key="@string/pref_default_viewer_key"
-            android:entries="@array/viewers"
-            android:entryValues="@array/viewers_values"
-            android:defaultValue="1"
-            android:summary="%s"/>
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:title="@string/pref_image_scale_type"
-            android:key="@string/pref_image_scale_type_key"
-            android:entries="@array/image_scale_type"
-            android:entryValues="@array/image_scale_type_values"
-            android:defaultValue="1"
-            android:summary="%s"/>
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:title="@string/pref_zoom_start"
-            android:key="@string/pref_zoom_start_key"
-            android:entries="@array/zoom_start"
-            android:entryValues="@array/zoom_start_values"
-            android:defaultValue="1"
-            android:summary="%s"/>
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:title="@string/pref_rotation_type"
-            android:key="@string/pref_rotation_type_key"
-            android:entries="@array/rotation_type"
-            android:entryValues="@array/rotation_type_values"
-            android:defaultValue="1"
-            android:summary="%s"/>
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:title="@string/pref_reader_theme"
-            android:key="@string/pref_reader_theme_key"
-            android:entries="@array/reader_themes"
-            android:entryValues="@array/reader_themes_values"
-            android:defaultValue="0"
-            android:summary="%s"/>
-
-        <eu.kanade.tachiyomi.widget.preference.IntListPreference
-            android:title="@string/pref_image_decoder"
-            android:key="@string/pref_image_decoder_key"
-            android:entries="@array/image_decoders"
-            android:entryValues="@array/image_decoders_values"
-            android:defaultValue="0"
-            android:summary="%s" />
-
-        <SwitchPreference
-            android:title="@string/pref_fullscreen"
-            android:key="@string/pref_fullscreen_key"
-            android:defaultValue="true" />
-
-        <SwitchPreference
-            android:title="@string/pref_page_transitions"
-            android:key="@string/pref_enable_transitions_key"
-            android:defaultValue="true" />
-
-        <SwitchPreference
-            android:title="@string/pref_show_page_number"
-            android:key="@string/pref_show_page_number_key"
-            android:defaultValue="true" />
-
-        <SwitchPreference
-            android:title="@string/pref_crop_borders"
-            android:key="@string/pref_crop_borders_key"
-            android:defaultValue="false" />
-
-        <SwitchPreference
-            android:title="@string/pref_keep_screen_on"
-            android:key="@string/pref_keep_screen_on_key"
-            android:defaultValue="true" />
-
-        <PreferenceCategory
-            android:title="@string/pref_reader_navigation">
-
-            <SwitchPreference
-                android:title="@string/pref_read_with_tapping"
-                android:key="@string/pref_read_with_tapping_key"
-                android:defaultValue="true" />
-
-            <SwitchPreference
-                android:title="@string/pref_read_with_volume_keys"
-                android:key="@string/pref_read_with_volume_keys_key"
-                android:defaultValue="false" />
-
-        </PreferenceCategory>
-
-    </PreferenceScreen>
-
-</PreferenceScreen>

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

@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-
-    <PreferenceScreen
-        android:icon="@drawable/ic_language_black_24dp"
-        android:key="sources_screen"
-        android:persistent="false"
-        android:title="@string/pref_category_sources"
-        app:asp_tintEnabled="true">
-
-        <!-- Dummy preference, it's needed at least one  -->
-        <Preference/>
-
-    </PreferenceScreen>
-
-</PreferenceScreen>

+ 0 - 33
app/src/main/res/xml/pref_tracking.xml

@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-
-    <PreferenceScreen
-        android:icon="@drawable/ic_sync_black_24dp"
-        android:key="tracking_screen"
-        android:persistent="false"
-        android:title="@string/pref_category_tracking"
-        app:asp_tintEnabled="true">
-
-        <SwitchPreference
-            android:key="@string/pref_auto_update_manga_sync_key"
-            android:title="@string/pref_auto_update_manga_sync"
-            android:defaultValue="true"
-            app:showText="false"/>
-
-        <SwitchPreference
-            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"
-            app:showText="false"/>
-
-        <PreferenceCategory
-            android:key="@string/pref_category_tracking_accounts_key"
-            android:title="@string/services"
-            android:persistent="false"
-            app:showText="false"/>
-
-    </PreferenceScreen>
-
-</PreferenceScreen>