mirror of https://github.com/tasks/tasks
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
502 lines
18 KiB
Kotlin
502 lines
18 KiB
Kotlin
package org.tasks.preferences
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.content.ContentResolver
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.content.SharedPreferences
|
|
import android.content.pm.PackageManager
|
|
import android.media.RingtoneManager
|
|
import android.net.Uri
|
|
import android.os.Binder
|
|
import androidx.core.app.NotificationCompat
|
|
import androidx.documentfile.provider.DocumentFile
|
|
import androidx.preference.PreferenceManager
|
|
import com.todoroo.andlib.utility.DateUtilities
|
|
import com.todoroo.andlib.utility.DateUtilities.now
|
|
import com.todoroo.astrid.activity.BeastModePreferences
|
|
import com.todoroo.astrid.api.AstridApiConstants
|
|
import com.todoroo.astrid.core.SortHelper
|
|
import com.todoroo.astrid.data.Task
|
|
import org.tasks.BuildConfig
|
|
import org.tasks.R
|
|
import org.tasks.Strings.isNullOrEmpty
|
|
import org.tasks.billing.Purchase
|
|
import org.tasks.data.TaskAttachment
|
|
import org.tasks.themes.ColorProvider
|
|
import org.tasks.themes.ThemeBase
|
|
import org.tasks.time.DateTime
|
|
import timber.log.Timber
|
|
import java.io.File
|
|
import java.net.URI
|
|
import java.util.*
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
class Preferences @JvmOverloads constructor(
|
|
private val context: Context,
|
|
name: String? = getSharedPreferencesName(context)
|
|
) : QueryPreferences {
|
|
private val prefs: SharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE)
|
|
private val publicPrefs: SharedPreferences = context.getSharedPreferences(AstridApiConstants.PUBLIC_PREFS, Context.MODE_PRIVATE)
|
|
|
|
fun androidBackupServiceEnabled() = getBoolean(R.string.p_backups_android_backup_enabled, true)
|
|
|
|
fun showBackupWarnings() = !getBoolean(R.string.p_backups_ignore_warnings, false)
|
|
|
|
fun addTasksToTop(): Boolean = getBoolean(R.string.p_add_to_top, true)
|
|
|
|
fun backButtonSavesTask(): Boolean = getBoolean(R.string.p_back_button_saves_task, false)
|
|
|
|
val isCurrentlyQuietHours: Boolean
|
|
get() {
|
|
if (quietHoursEnabled()) {
|
|
val dateTime = DateTime()
|
|
val start = dateTime.withMillisOfDay(quietHoursStart)
|
|
val end = dateTime.withMillisOfDay(quietHoursEnd)
|
|
return if (start.isAfter(end)) {
|
|
dateTime.isBefore(end) || dateTime.isAfter(start)
|
|
} else {
|
|
dateTime.isAfter(start) && dateTime.isBefore(end)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
fun adjustForQuietHours(time: Long): Long {
|
|
if (quietHoursEnabled()) {
|
|
val dateTime = DateTime(time)
|
|
val start = dateTime.withMillisOfDay(quietHoursStart)
|
|
val end = dateTime.withMillisOfDay(quietHoursEnd)
|
|
if (start.isAfter(end)) {
|
|
if (dateTime.isBefore(end)) {
|
|
return end.millis
|
|
} else if (dateTime.isAfter(start)) {
|
|
return end.plusDays(1).millis
|
|
}
|
|
} else {
|
|
if (dateTime.isAfter(start) && dateTime.isBefore(end)) {
|
|
return end.millis
|
|
}
|
|
}
|
|
}
|
|
return time
|
|
}
|
|
|
|
private fun quietHoursEnabled(): Boolean = getBoolean(R.string.p_rmd_enable_quiet, false)
|
|
|
|
val defaultDueTime: Int
|
|
get() = getInt(R.string.p_rmd_time, TimeUnit.HOURS.toMillis(18).toInt())
|
|
|
|
private val quietHoursStart: Int
|
|
get() = getMillisPerDayPref(R.string.p_rmd_quietStart, R.integer.default_quiet_hours_start)
|
|
|
|
private val quietHoursEnd: Int
|
|
get() = getMillisPerDayPref(R.string.p_rmd_quietEnd, R.integer.default_quiet_hours_end)
|
|
|
|
val dateShortcutMorning: Int
|
|
get() = getMillisPerDayPref(R.string.p_date_shortcut_morning, R.integer.default_morning)
|
|
|
|
val dateShortcutAfternoon: Int
|
|
get() = getMillisPerDayPref(R.string.p_date_shortcut_afternoon, R.integer.default_afternoon)
|
|
|
|
val dateShortcutEvening: Int
|
|
get() = getMillisPerDayPref(R.string.p_date_shortcut_evening, R.integer.default_evening)
|
|
|
|
val purchases: List<Purchase>
|
|
get() = try {
|
|
prefs.getStringSet(context.getString(R.string.p_purchases), emptySet())!!.map(::Purchase)
|
|
} catch (e: Exception) {
|
|
Timber.e(e)
|
|
emptyList()
|
|
}
|
|
|
|
fun setPurchases(purchases: Collection<Purchase>) {
|
|
setPurchases(purchases.mapNotNull(Purchase::toJson).toHashSet())
|
|
}
|
|
|
|
fun setPurchases(set: HashSet<String>) {
|
|
try {
|
|
val editor = prefs.edit()
|
|
editor.putStringSet(context.getString(R.string.p_purchases), set)
|
|
editor.apply()
|
|
} catch (e: Exception) {
|
|
Timber.e(e)
|
|
}
|
|
}
|
|
|
|
val dateShortcutNight: Int
|
|
get() = getMillisPerDayPref(R.string.p_date_shortcut_night, R.integer.default_night)
|
|
|
|
private fun getMillisPerDayPref(resId: Int, defResId: Int): Int {
|
|
val setting = getInt(resId, -1)
|
|
return if (setting < 0 || setting > DateTime.MAX_MILLIS_PER_DAY) {
|
|
context.resources.getInteger(defResId)
|
|
} else setting
|
|
}
|
|
|
|
val isDefaultCalendarSet: Boolean
|
|
get() {
|
|
val defaultCalendar = defaultCalendar
|
|
return defaultCalendar != null && defaultCalendar != "-1" && defaultCalendar != "0"
|
|
}
|
|
|
|
val ringtone: Uri?
|
|
get() {
|
|
val ringtone = getStringValue(R.string.p_rmd_ringtone)
|
|
?: return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
|
|
return if ("" == ringtone) {
|
|
null
|
|
} else Uri.parse(ringtone)
|
|
}
|
|
|
|
val isTrackingEnabled: Boolean
|
|
get() = getBoolean(R.string.p_collect_statistics, true)
|
|
|
|
val defaultCalendar: String?
|
|
get() = getStringValue(R.string.gcal_p_default)
|
|
|
|
val firstDayOfWeek: Int
|
|
get() {
|
|
val firstDayOfWeek = getIntegerFromString(R.string.p_start_of_week, 0)
|
|
return if (firstDayOfWeek < 1 || firstDayOfWeek > 7) 0 else firstDayOfWeek
|
|
}
|
|
|
|
@SuppressLint("ApplySharedPref")
|
|
fun clear() {
|
|
publicPrefs.edit().clear().commit()
|
|
prefs.edit().clear().commit()
|
|
}
|
|
|
|
fun setDefaults() {
|
|
PreferenceManager.setDefaultValues(context, R.xml.preferences, true)
|
|
PreferenceManager.setDefaultValues(context, R.xml.preferences_look_and_feel, true)
|
|
PreferenceManager.setDefaultValues(context, R.xml.preferences_notifications, true)
|
|
PreferenceManager.setDefaultValues(context, R.xml.preferences_task_defaults, true)
|
|
PreferenceManager.setDefaultValues(context, R.xml.preferences_location, true)
|
|
PreferenceManager.setDefaultValues(context, R.xml.preferences_date_and_time, true)
|
|
PreferenceManager.setDefaultValues(context, R.xml.preferences_navigation_drawer, true)
|
|
PreferenceManager.setDefaultValues(context, R.xml.preferences_backups, true)
|
|
PreferenceManager.setDefaultValues(context, R.xml.preferences_advanced, true)
|
|
PreferenceManager.setDefaultValues(context, R.xml.help_and_feedback, true)
|
|
BeastModePreferences.setDefaultOrder(this, context)
|
|
}
|
|
|
|
fun reset() {
|
|
clear()
|
|
setDefaults()
|
|
}
|
|
|
|
fun getStringValue(keyResource: Int): String? = getStringValue(context.getString(keyResource))
|
|
|
|
fun getStringValue(key: String?): String? = try {
|
|
prefs.getString(key, null)
|
|
} catch (e: Exception) {
|
|
Timber.e(e)
|
|
null
|
|
}
|
|
|
|
val defaultReminders: Int
|
|
get() = getIntegerFromString(
|
|
R.string.p_default_reminders_key, Task.NOTIFY_AT_DEADLINE or Task.NOTIFY_AFTER_DEADLINE)
|
|
|
|
val defaultRingMode: Int
|
|
get() = getIntegerFromString(R.string.p_default_reminders_mode_key, 0)
|
|
|
|
val fontSize: Int
|
|
get() = getInt(R.string.p_fontSize, 16)
|
|
|
|
val mapTheme: Int
|
|
get() = getIntegerFromString(R.string.p_map_theme, 0)
|
|
|
|
fun getIntegerFromString(keyResource: Int, defaultValue: Int): Int =
|
|
getIntegerFromString(context.getString(keyResource), defaultValue)
|
|
|
|
fun getIntegerFromString(keyResource: String?, defaultValue: Int): Int =
|
|
getStringValue(keyResource)?.toIntOrNull() ?: defaultValue
|
|
|
|
private fun getUri(key: Int): Uri? {
|
|
val uri = getStringValue(key)
|
|
return if (isNullOrEmpty(uri)) null else Uri.parse(uri)
|
|
}
|
|
|
|
fun setUri(key: Int, uri: URI) {
|
|
setString(key, uri.toString())
|
|
}
|
|
|
|
fun setUri(key: Int, uri: Uri) {
|
|
setString(key, uri.toString())
|
|
}
|
|
|
|
fun setString(key: Int, newValue: String?) {
|
|
setString(context.getString(key), newValue)
|
|
}
|
|
|
|
fun setString(key: String?, newValue: String?) {
|
|
val editor = prefs.edit()
|
|
editor.putString(key, newValue)
|
|
editor.apply()
|
|
}
|
|
|
|
fun setStringFromInteger(keyResource: Int, newValue: Int) {
|
|
val editor = prefs.edit()
|
|
editor.putString(context.getString(keyResource), newValue.toString())
|
|
editor.apply()
|
|
}
|
|
|
|
fun getBoolean(key: String?, defValue: Boolean): Boolean = try {
|
|
prefs.getBoolean(key, defValue)
|
|
} catch (e: ClassCastException) {
|
|
Timber.w(e)
|
|
defValue
|
|
}
|
|
|
|
fun getBoolean(keyResources: Int, defValue: Boolean): Boolean =
|
|
getBoolean(context.getString(keyResources), defValue)
|
|
|
|
fun setBoolean(keyResource: Int, value: Boolean) {
|
|
setBoolean(context.getString(keyResource), value)
|
|
}
|
|
|
|
fun setBoolean(key: String?, value: Boolean) {
|
|
val editor = prefs.edit()
|
|
editor.putBoolean(key, value)
|
|
editor.apply()
|
|
}
|
|
|
|
fun getInt(resourceId: Int, defValue: Int): Int =
|
|
getInt(context.getString(resourceId), defValue)
|
|
|
|
fun getInt(key: String?, defValue: Int): Int = prefs.getInt(key, defValue)
|
|
|
|
fun setInt(resourceId: Int, value: Int) {
|
|
setInt(context.getString(resourceId), value)
|
|
}
|
|
|
|
fun setInt(key: String?, value: Int) {
|
|
val editor = prefs.edit()
|
|
editor.putInt(key, value)
|
|
editor.apply()
|
|
}
|
|
|
|
fun getLong(resourceId: Int, defValue: Long): Long =
|
|
getLong(context.getString(resourceId), defValue)
|
|
|
|
fun getLong(key: String?, defValue: Long): Long = prefs.getLong(key, defValue)
|
|
|
|
fun setLong(resourceId: Int, value: Long) {
|
|
setLong(context.getString(resourceId), value)
|
|
}
|
|
|
|
fun setLong(key: String?, value: Long) {
|
|
val editor = prefs.edit()
|
|
editor.putLong(key, value)
|
|
editor.apply()
|
|
}
|
|
|
|
fun clear(key: String?) {
|
|
val editor = prefs.edit()
|
|
editor.remove(key)
|
|
editor.apply()
|
|
}
|
|
|
|
val lastSetVersion: Int
|
|
get() = getInt(R.string.p_current_version, 0)
|
|
|
|
fun setCurrentVersion(version: Int) {
|
|
setInt(R.string.p_current_version, version)
|
|
}
|
|
|
|
override var sortMode: Int
|
|
get() = publicPrefs.getInt(PREF_SORT_SORT, SortHelper.SORT_AUTO)
|
|
set(value) {
|
|
setPublicPref(PREF_SORT_SORT, value)
|
|
}
|
|
|
|
override var showHidden: Boolean
|
|
get() = getBoolean(R.string.p_show_hidden_tasks, true)
|
|
set(value) { setBoolean(R.string.p_show_hidden_tasks, value) }
|
|
|
|
override var showCompleted: Boolean
|
|
get() = getBoolean(R.string.p_show_completed_tasks, false)
|
|
set(value) { setBoolean(R.string.p_show_completed_tasks, value) }
|
|
|
|
override val showCompletedTemporarily: Boolean
|
|
get() = getBoolean(R.string.p_temporarily_show_completed_tasks, false)
|
|
|
|
override var alwaysDisplayFullDate: Boolean
|
|
get() = getBoolean(R.string.p_always_display_full_date, false)
|
|
set(value) { setBoolean(R.string.p_always_display_full_date, value)}
|
|
|
|
private fun setPublicPref(key: String, value: Int) {
|
|
val edit = publicPrefs.edit()
|
|
edit?.putInt(key, value)?.apply()
|
|
}
|
|
|
|
val backupDirectory: Uri?
|
|
get() = getDirectory(R.string.p_backup_dir, "backups")
|
|
|
|
val externalStorage: Uri
|
|
get() = root.uri
|
|
|
|
val attachmentsDirectory: Uri?
|
|
get() = getDirectory(R.string.p_attachment_dir, TaskAttachment.FILES_DIRECTORY_DEFAULT)
|
|
|
|
private fun getDirectory(pref: Int, name: String): Uri? {
|
|
val uri = getUri(pref)
|
|
if (uri != null) {
|
|
when (uri.scheme) {
|
|
ContentResolver.SCHEME_FILE -> {
|
|
val file = File(uri.path)
|
|
try {
|
|
if (file.canWrite()) {
|
|
return uri
|
|
}
|
|
} catch (ignored: SecurityException) {
|
|
}
|
|
}
|
|
ContentResolver.SCHEME_CONTENT -> if (hasWritePermission(context, uri)) {
|
|
return uri
|
|
}
|
|
}
|
|
}
|
|
return getDefaultDirectory(name)
|
|
}
|
|
|
|
private fun getDefaultDirectory(name: String): Uri? =
|
|
root
|
|
.createDirectory(name)
|
|
?.uri
|
|
?: getDefaultFileLocation(name)?.let { Uri.fromFile(it) }
|
|
|
|
private val root: DocumentFile
|
|
get() = DocumentFile.fromFile(context.getExternalFilesDir(null)!!)
|
|
|
|
private fun getDefaultFileLocation(type: String): File? {
|
|
val externalFilesDir = context.getExternalFilesDir(null) ?: return null
|
|
val path = String.format("%s/%s", externalFilesDir.absolutePath, type)
|
|
val file = File(path)
|
|
return if (file.isDirectory || file.mkdirs()) file else null
|
|
}
|
|
|
|
val cacheDirectory: Uri
|
|
get() {
|
|
var cacheDir = context.externalCacheDir
|
|
if (cacheDir == null) {
|
|
cacheDir = context.cacheDir
|
|
}
|
|
return DocumentFile.fromFile(cacheDir!!).uri
|
|
}
|
|
|
|
private fun hasWritePermission(context: Context, uri: Uri): Boolean =
|
|
(PackageManager.PERMISSION_GRANTED
|
|
== context.checkUriPermission(
|
|
uri,
|
|
Binder.getCallingPid(),
|
|
Binder.getCallingUid(),
|
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION))
|
|
|
|
val notificationDefaults: Int
|
|
get() {
|
|
var result = 0
|
|
if (getBoolean(R.string.p_rmd_vibrate, true)) {
|
|
result = result or NotificationCompat.DEFAULT_VIBRATE
|
|
}
|
|
if (getBoolean(R.string.p_led_notification, true)) {
|
|
result = result or NotificationCompat.DEFAULT_LIGHTS
|
|
}
|
|
return result
|
|
}
|
|
|
|
fun remove(resId: Int) {
|
|
val editor = prefs.edit()
|
|
editor.remove(context.getString(resId))
|
|
editor.apply()
|
|
}
|
|
|
|
fun bundleNotifications(): Boolean = getBoolean(R.string.p_bundle_notifications, true)
|
|
|
|
fun usePersistentReminders(): Boolean = getBoolean(R.string.p_rmd_persistent, true)
|
|
|
|
var isSyncOngoing: Boolean
|
|
get() = syncFlags.any { getBoolean(it, false) }
|
|
set(value) {
|
|
syncFlags.forEach { setBoolean(it, value) }
|
|
}
|
|
|
|
fun <T> getPrefs(c: Class<T>): Map<String, T> {
|
|
val result: MutableMap<String, T> = HashMap()
|
|
val entries: Iterable<Map.Entry<String, *>> = prefs.all.entries.filter { e: Map.Entry<String?, Any?> -> c.isInstance(e.value) }
|
|
for ((key, value) in entries) {
|
|
result[key] = value as T
|
|
}
|
|
return result
|
|
}
|
|
|
|
val isFlipperEnabled: Boolean
|
|
get() = BuildConfig.DEBUG && getBoolean(R.string.p_flipper, false)
|
|
|
|
var isPositionHackEnabled: Boolean
|
|
get() = getLong(R.string.p_google_tasks_position_hack, 0) > now() - DateUtilities.ONE_WEEK
|
|
set(value) { setLong(R.string.p_google_tasks_position_hack, if (value) now() else 0) }
|
|
|
|
override var isManualSort: Boolean
|
|
get() = getBoolean(R.string.p_manual_sort, false)
|
|
set(value) { setBoolean(R.string.p_manual_sort, value) }
|
|
|
|
override var isAstridSort: Boolean
|
|
get() = getBoolean(R.string.p_astrid_sort_enabled, false) && getBoolean(R.string.p_astrid_sort, false)
|
|
set(value) { setBoolean(R.string.p_astrid_sort, value) }
|
|
|
|
override var isReverseSort: Boolean
|
|
get() = getBoolean(R.string.p_reverse_sort, false)
|
|
set(value) { setBoolean(R.string.p_reverse_sort, value) }
|
|
|
|
val defaultPriority: Int
|
|
get() = getIntegerFromString(R.string.p_default_importance_key, Task.Priority.LOW)
|
|
|
|
val themeBase: Int
|
|
get() = getInt(R.string.p_theme, ThemeBase.DEFAULT_BASE_THEME)
|
|
|
|
val showSubtaskChip: Boolean
|
|
get() = getBoolean(R.string.p_subtask_chips, true)
|
|
|
|
val showStartDateChip: Boolean
|
|
get() = getBoolean(R.string.p_start_date_chip, true)
|
|
|
|
val showPlaceChip: Boolean
|
|
get() = getBoolean(R.string.p_place_chips, true)
|
|
|
|
val showListChip: Boolean
|
|
get() = getBoolean(R.string.p_list_chips, true)
|
|
|
|
val showTagChip: Boolean
|
|
get() = getBoolean(R.string.p_tag_chips, true)
|
|
|
|
fun alreadyNotified(account: String?, scope: String?): Boolean =
|
|
getBoolean(context.getString(R.string.p_notified_oauth_error, account, scope), false)
|
|
|
|
fun setAlreadyNotified(account: String?, scope: String?, value: Boolean) {
|
|
setBoolean(context.getString(R.string.p_notified_oauth_error, account, scope), value)
|
|
}
|
|
|
|
val defaultThemeColor: Int
|
|
get() = getInt(R.string.p_theme_color, ColorProvider.BLUE_500)
|
|
|
|
override fun usePagedQueries(): Boolean = getBoolean(R.string.p_use_paged_queries, false)
|
|
|
|
fun showGroupHeaders(): Boolean =
|
|
!usePagedQueries() && !getBoolean(R.string.p_disable_sort_groups, false)
|
|
|
|
companion object {
|
|
private const val PREF_SORT_SORT = "sort_sort" // $NON-NLS-1$
|
|
|
|
private fun getSharedPreferencesName(context: Context): String =
|
|
context.packageName + "_preferences"
|
|
|
|
private val syncFlags = listOf(
|
|
R.string.p_sync_ongoing_google_tasks,
|
|
R.string.p_sync_ongoing_caldav,
|
|
R.string.p_sync_ongoing_etesync,
|
|
R.string.p_sync_ongoing_android)
|
|
}
|
|
} |