From ff190e02d455e6c154d88ff1b5c041c38d887035 Mon Sep 17 00:00:00 2001 From: inorichi Date: Sun, 14 May 2017 00:45:14 +0200 Subject: [PATCH] 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 --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 4 - .../tachiyomi/data/backup/BackupConst.kt | 23 + .../data/backup/BackupCreateService.kt | 19 +- .../tachiyomi/data/backup/BackupCreatorJob.kt | 2 +- .../tachiyomi/data/backup/BackupManager.kt | 1 - .../data/backup/BackupRestoreService.kt | 43 +- .../data/preference/PreferenceKeys.kt | 234 +++++---- .../data/preference/PreferencesHelper.kt | 117 +++-- .../ui/base/controller/BaseController.kt | 10 - .../ui/base/controller/ConductorExtensions.kt | 12 + .../kanade/tachiyomi/ui/main/MainActivity.kt | 30 +- .../ui/manga/chapter/ChaptersController.kt | 1 + .../RecentChaptersController.kt | 3 +- .../ui/setting/AnilistLoginActivity.kt | 3 +- .../tachiyomi/ui/setting/PreferenceDSL.kt | 102 ++++ .../ui/setting/SettingsAboutController.kt | 166 +++++++ .../ui/setting/SettingsAboutFragment.kt | 139 ------ .../tachiyomi/ui/setting/SettingsActivity.kt | 86 ---- .../ui/setting/SettingsAdvancedController.kt | 159 ++++++ .../ui/setting/SettingsAdvancedFragment.kt | 117 ----- .../ui/setting/SettingsBackupController.kt | 458 ++++++++++++++++++ .../ui/setting/SettingsBackupFragment.kt | 407 ---------------- .../ui/setting/SettingsController.kt | 70 +++ .../ui/setting/SettingsDownloadController.kt | 186 +++++++ .../ui/setting/SettingsDownloadsFragment.kt | 149 ------ .../tachiyomi/ui/setting/SettingsFragment.kt | 62 --- .../ui/setting/SettingsGeneralController.kt | 225 +++++++++ .../ui/setting/SettingsGeneralFragment.kt | 166 ------- .../ui/setting/SettingsMainController.kt | 70 +++ .../ui/setting/SettingsReaderController.kt | 106 ++++ ...agment.kt => SettingsSourcesController.kt} | 70 +-- .../ui/setting/SettingsTrackingController.kt | 91 ++++ .../ui/setting/SettingsTrackingFragment.kt | 95 ---- .../java/eu/kanade/tachiyomi/util/DiskUtil.kt | 9 +- .../preference/LoginCheckBoxPreference.kt | 2 +- .../preference/LoginDialogPreference.kt | 29 +- .../widget/preference/SourceLoginDialog.kt | 40 +- .../preference/SwitchPreferenceCategory.kt | 14 +- .../widget/preference/TrackLoginDialog.kt | 46 +- .../main/res/layout/pref_library_columns.xml | 2 + app/src/main/res/values/keys.xml | 76 --- app/src/main/res/values/themes.xml | 4 - app/src/main/res/xml/pref_about.xml | 37 -- app/src/main/res/xml/pref_advanced.xml | 33 -- app/src/main/res/xml/pref_backup.xml | 48 -- app/src/main/res/xml/pref_downloads.xml | 62 --- app/src/main/res/xml/pref_general.xml | 76 --- app/src/main/res/xml/pref_reader.xml | 103 ---- app/src/main/res/xml/pref_sources.xml | 18 - app/src/main/res/xml/pref_tracking.xml | 33 -- 51 files changed, 1969 insertions(+), 2091 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupFragment.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadsFragment.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/setting/{SettingsSourcesFragment.kt => SettingsSourcesController.kt} (63%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingFragment.kt delete mode 100644 app/src/main/res/xml/pref_about.xml delete mode 100644 app/src/main/res/xml/pref_advanced.xml delete mode 100644 app/src/main/res/xml/pref_backup.xml delete mode 100644 app/src/main/res/xml/pref_downloads.xml delete mode 100644 app/src/main/res/xml/pref_general.xml delete mode 100644 app/src/main/res/xml/pref_reader.xml delete mode 100644 app/src/main/res/xml/pref_sources.xml delete mode 100644 app/src/main/res/xml/pref_tracking.xml diff --git a/app/build.gradle b/app/build.gradle index 6ee2111ba7..c2e063e9c2 100644 --- a/app/build.gradle +++ b/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' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 23b3854e55..953006fb53 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,10 +35,6 @@ - () 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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt index 2fc1998d56..812434abe6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt +++ b/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) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt index 02df6b303d..dc2d0eb05b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt +++ b/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(EXTRA_URI) + val uri = intent.getParcelableExtra(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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index b687b8d3d2..fb4f6f45b0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/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" + +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index de56700f42..4559c6ab95 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -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 Preference.getOrDefault(): T = get() ?: defaultValue()!! @@ -17,8 +18,6 @@ fun Preference.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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt index e8fff9e714..f8cc74a308 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt +++ b/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 - } - } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/ConductorExtensions.kt new file mode 100644 index 0000000000..509ec5ae7a --- /dev/null +++ b/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 +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index a90a3b6c3c..5eac8dde2a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/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" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt index fa43e6e443..378fde4798 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt +++ b/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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt index eda0d3ce67..d73e0cd158 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersController.kt +++ b/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(), actionMode = null } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt index b49282531a..af0b6ffc78 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/AnilistLoginActivity.kt +++ b/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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt new file mode 100644 index 0000000000..0c496f0eb9 --- /dev/null +++ b/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

PreferenceGroup.initThenAdd(p: P, block: P.() -> Unit): P { + return p.apply { block(); addPreference(this); } +} + +inline fun

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 + get() = emptyArray() // set only + set(value) { entries = value.map { context.getString(it) }.toTypedArray() } + +var MultiSelectListPreference.entriesRes: Array + get() = emptyArray() // set only + set(value) { entries = value.map { context.getString(it) }.toTypedArray() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutController.kt new file mode 100644 index 0000000000..3aa9398e95 --- /dev/null +++ b/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 + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt deleted file mode 100644 index 1f445cd5d6..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAboutFragment.kt +++ /dev/null @@ -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) - }) - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt deleted file mode 100644 index 21304dae57..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt +++ /dev/null @@ -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 - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt new file mode 100644 index 0000000000..da58c80927 --- /dev/null +++ b/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" + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt deleted file mode 100644 index e8576414d2..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedFragment.kt +++ /dev/null @@ -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() - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt new file mode 100644 index 0000000000..8c6d28dd25 --- /dev/null +++ b/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(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(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" + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupFragment.kt deleted file mode 100644 index 7ef9a70cd8..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupFragment.kt +++ /dev/null @@ -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(this@SettingsBackupFragment.context) - .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(this@SettingsBackupFragment.context) - .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 - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsController.kt new file mode 100644 index 0000000000..f78a4c98cd --- /dev/null +++ b/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 Observable.subscribeUntilDestroy(): Subscription { + return subscribe().also { untilDestroySubscriptions.add(it) } + } + + fun Observable.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription { + return subscribe(onNext).also { untilDestroySubscriptions.add(it) } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt new file mode 100644 index 0000000000..71a6409d0b --- /dev/null +++ b/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 { + 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadsFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadsFragment.kt deleted file mode 100644 index 09d56d5989..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadsFragment.kt +++ /dev/null @@ -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 { - 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()) - } - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt deleted file mode 100644 index 291db41798..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsFragment.kt +++ /dev/null @@ -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 bindPref(resId: Int): Lazy { - return lazy { findPreference(getString(resId)) as T } - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt new file mode 100644 index 0000000000..294e858349 --- /dev/null +++ b/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 + } + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt deleted file mode 100644 index bc872fb140..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralFragment.kt +++ /dev/null @@ -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() - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt new file mode 100644 index 0000000000..4953429fb8 --- /dev/null +++ b/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())) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt new file mode 100644 index 0000000000..229a709a62 --- /dev/null +++ b/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 + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt similarity index 63% rename from app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesController.kt index 6f9349a5db..c7982d0d88 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSourcesFragment.kt +++ b/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().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) { + private fun addLanguageSources(group: PreferenceGroup, sources: List) { 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" } -} +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingController.kt new file mode 100644 index 0000000000..0f7251bbf4 --- /dev/null +++ b/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) + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingFragment.kt deleted file mode 100644 index 922d83958b..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsTrackingFragment.kt +++ /dev/null @@ -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 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() - } - -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt index 1f1587fe68..ce231756c5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/DiskUtil.kt +++ b/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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt index a905b9f59b..ac45bd4afc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginCheckBoxPreference.kt +++ b/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, diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt index a83d9712b2..1b355d4905 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt +++ b/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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt index b197ba6a86..42cf0b18ec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SourceLoginDialog.kt +++ b/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().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) + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt index f07ccbc726..cff0847c10 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/SwitchPreferenceCategory.kt +++ b/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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt index 58da50ff20..f9fc355c0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/TrackLoginDialog.kt +++ b/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().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) + } + } diff --git a/app/src/main/res/layout/pref_library_columns.xml b/app/src/main/res/layout/pref_library_columns.xml index 7db9c0163b..eab84cc2e7 100644 --- a/app/src/main/res/layout/pref_library_columns.xml +++ b/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"/> diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index bee25b223b..6f662810f2 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -1,81 +1,5 @@ - pref_category_general_key - pref_category_reader_key - pref_category_tracking_key - pref_category_downloads_key - pref_category_advanced_key - pref_category_about_key - pref_category_sources_key - - pref_display_library_as_list - pref_library_columns_dialog_key - pref_library_columns_portrait_key - pref_library_columns_landscape_key - pref_library_update_interval_key - library_update_categories - pref_update_only_non_completed_key - pref_auto_update_manga_sync_key - pref_ask_update_manga_sync_key - pref_theme_key - library_update_restriction - start_screen - app_language - default_category - - pref_default_viewer_key - pref_image_scale_type_key - pref_zoom_start_key - fullscreen - pref_rotation_type_key - pref_enable_transitions_key - pref_show_page_number_key - pref_keep_screen_on_key - pref_custom_brightness_key - custom_brightness_value - pref_color_filter_key - color_filter_value - pref_red_filter_value - pref_reader_theme_key - image_decoder - crop_borders - reader_volume_keys - reader_tap - - pref_filter_downloaded_key - pref_filter_unread_key - library_sorting_mode - - download_directory - pref_download_slots_key - remove_after_read_slots - pref_download_only_over_wifi_key - pref_remove_after_marked_as_read_key - last_used_category - - create_local_backup - restore_local_backup - backup_interval - backup_directory - backup_slots - - source_languages - category_tracking_accounts - - pref_clear_chapter_cache_key - pref_clear_database_key - pref_clear_cookies_key - refresh_library_metadata - - pref_version - pref_build_time - automatic_updates - - pref_display_catalogue_as_list - last_catalogue_source - - download_new - download_new_categories sans-serif diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index f4c0714a05..8a82972a37 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -36,8 +36,6 @@ @drawable/library_item_selector_light @color/textColorPrimaryLight @color/dialogLight - ?colorAccent - ?colorAccent