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
pull/798/head
inorichi 7 years ago committed by GitHub
parent 29fd5747eb
commit ff190e02d4

@ -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'

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

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

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

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

@ -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) {

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

@ -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"
}

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

@ -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
}
}

@ -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
}

@ -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"

@ -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

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

@ -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)
}

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

@ -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
}
}
}

@ -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)
})
}
}

@ -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
}
}

@ -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"
}
}

@ -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()
}
}

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

@ -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
}
}

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

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

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

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

@ -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
}
}
}
}
}

@ -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()
}
}

@ -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()))
}
}

@ -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
}
}
}
}

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

@ -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)
}
}

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

@ -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)
}

@ -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,

@ -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()

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

@ -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)
}

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

@ -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"/>

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

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

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

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

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

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

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

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

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

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