Use coroutines in preference fragments

pull/1047/head
Alex Baker 6 years ago
parent 443ac9e9e5
commit c36649fb54

@ -21,7 +21,7 @@ class Debug : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences_debug override fun getPreferenceXml() = R.xml.preferences_debug
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
for (pref in listOf( for (pref in listOf(
R.string.p_leakcanary, R.string.p_leakcanary,
R.string.p_flipper, R.string.p_flipper,

@ -2,10 +2,12 @@ package org.tasks.injection
import android.os.Bundle import android.os.Bundle
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceGroup import androidx.preference.PreferenceGroup
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import kotlinx.coroutines.launch
import org.tasks.R import org.tasks.R
import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.DialogBuilder
import org.tasks.preferences.Device import org.tasks.preferences.Device
@ -40,7 +42,9 @@ abstract class InjectingPreferenceFragment : PreferenceFragmentCompat() {
tintIcons(preferenceScreen, requireContext().getColor(R.color.icon_tint_with_alpha)) tintIcons(preferenceScreen, requireContext().getColor(R.color.icon_tint_with_alpha))
setupPreferences(savedInstanceState) lifecycleScope.launch {
setupPreferences(savedInstanceState)
}
} }
protected open fun showRestartDialog() { protected open fun showRestartDialog() {
@ -90,7 +94,7 @@ abstract class InjectingPreferenceFragment : PreferenceFragmentCompat() {
abstract fun getPreferenceXml(): Int abstract fun getPreferenceXml(): Int
abstract fun setupPreferences(savedInstanceState: Bundle?) abstract suspend fun setupPreferences(savedInstanceState: Bundle?)
protected fun recreate() { protected fun recreate() {
requireActivity().recreate() requireActivity().recreate()

@ -10,8 +10,8 @@ import dagger.hilt.android.components.ApplicationComponent
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.data.CaldavDaoBlocking import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskListDaoBlocking import org.tasks.data.GoogleTaskListDao
import org.tasks.db.Migrations import org.tasks.db.Migrations
import org.tasks.jobs.WorkManager import org.tasks.jobs.WorkManager
import org.tasks.jobs.WorkManagerImpl import org.tasks.jobs.WorkManagerImpl
@ -40,8 +40,8 @@ internal class ProductionModule {
fun getWorkManager( fun getWorkManager(
@ApplicationContext context: Context, @ApplicationContext context: Context,
preferences: Preferences, preferences: Preferences,
googleTaskListDao: GoogleTaskListDaoBlocking, googleTaskListDao: GoogleTaskListDao,
caldavDao: CaldavDaoBlocking): WorkManager { caldavDao: CaldavDao): WorkManager {
return WorkManagerImpl(context, preferences, googleTaskListDao, caldavDao) return WorkManagerImpl(context, preferences, googleTaskListDao, caldavDao)
} }
} }

@ -17,7 +17,7 @@ interface WorkManager {
fun updateBackgroundSync() fun updateBackgroundSync()
fun updateBackgroundSync( suspend fun updateBackgroundSync(
forceAccountPresent: Boolean?, forceAccountPresent: Boolean?,
forceBackgroundEnabled: Boolean?, forceBackgroundEnabled: Boolean?,
forceOnlyOnUnmetered: Boolean?) forceOnlyOnUnmetered: Boolean?)

@ -10,14 +10,11 @@ import androidx.work.*
import com.todoroo.andlib.utility.AndroidUtilities import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import io.reactivex.Single import kotlinx.coroutines.runBlocking
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.data.CaldavDaoBlocking import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskListDaoBlocking import org.tasks.data.GoogleTaskListDao
import org.tasks.data.Place import org.tasks.data.Place
import org.tasks.date.DateTimeUtils.midnight import org.tasks.date.DateTimeUtils.midnight
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
@ -39,8 +36,8 @@ import kotlin.math.max
class WorkManagerImpl constructor( class WorkManagerImpl constructor(
private val context: Context, private val context: Context,
private val preferences: Preferences, private val preferences: Preferences,
private val googleTaskListDao: GoogleTaskListDaoBlocking, private val googleTaskListDao: GoogleTaskListDao,
private val caldavDao: CaldavDaoBlocking): WorkManager { private val caldavDao: CaldavDao): WorkManager {
private val alarmManager: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager private val alarmManager: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
private val workManager = androidx.work.WorkManager.getInstance(context) private val workManager = androidx.work.WorkManager.getInstance(context)
@ -96,10 +93,12 @@ class WorkManagerImpl constructor(
.build()) .build())
} }
override fun updateBackgroundSync() = updateBackgroundSync(null, null, null) override fun updateBackgroundSync() = runBlocking {
updateBackgroundSync(null, null, null)
}
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
override fun updateBackgroundSync( override suspend fun updateBackgroundSync(
forceAccountPresent: Boolean?, forceAccountPresent: Boolean?,
forceBackgroundEnabled: Boolean?, forceBackgroundEnabled: Boolean?,
forceOnlyOnUnmetered: Boolean?) { forceOnlyOnUnmetered: Boolean?) {
@ -107,13 +106,10 @@ class WorkManagerImpl constructor(
?: preferences.getBoolean(R.string.p_background_sync, true) ?: preferences.getBoolean(R.string.p_background_sync, true)
val onlyOnWifi = forceOnlyOnUnmetered val onlyOnWifi = forceOnlyOnUnmetered
?: preferences.getBoolean(R.string.p_background_sync_unmetered_only, false) ?: preferences.getBoolean(R.string.p_background_sync_unmetered_only, false)
(if (forceAccountPresent == null) Single.zip( val accountsPresent = forceAccountPresent == true
Single.fromCallable { googleTaskListDao.accountCount() }, || googleTaskListDao.accountCount() > 0
Single.fromCallable { caldavDao.accountCount() }, || caldavDao.accountCount() > 0
BiFunction { googleCount: Int, caldavCount: Int -> googleCount > 0 || caldavCount > 0 }) else Single.just(forceAccountPresent)) scheduleBackgroundSync(backgroundEnabled && accountsPresent, onlyOnWifi)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { accountsPresent: Boolean -> scheduleBackgroundSync(backgroundEnabled && accountsPresent, onlyOnWifi) }
} }
private fun scheduleBackgroundSync(enabled: Boolean, onlyOnUnmetered: Boolean) { private fun scheduleBackgroundSync(enabled: Boolean, onlyOnUnmetered: Boolean) {

@ -47,7 +47,7 @@ class Advanced : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences_advanced override fun getPreferenceXml() = R.xml.preferences_advanced
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
findPreference(R.string.p_use_paged_queries) findPreference(R.string.p_use_paged_queries)
.setOnPreferenceChangeListener { _: Preference?, _: Any? -> .setOnPreferenceChangeListener { _: Preference?, _: Any? ->
localBroadcastManager.broadcastRefresh() localBroadcastManager.broadcastRefresh()

@ -38,7 +38,7 @@ class Backups : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences_backups override fun getPreferenceXml() = R.xml.preferences_backups
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
initializeBackupDirectory() initializeBackupDirectory()
findPreference(R.string.backup_BAc_import) findPreference(R.string.backup_BAc_import)

@ -22,7 +22,7 @@ class DashClock : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences_dashclock override fun getPreferenceXml() = R.xml.preferences_dashclock
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
findPreference(R.string.p_dashclock_filter) findPreference(R.string.p_dashclock_filter)
.setOnPreferenceClickListener { .setOnPreferenceClickListener {
val intent = Intent(context, FilterSelectionActivity::class.java) val intent = Intent(context, FilterSelectionActivity::class.java)

@ -31,7 +31,7 @@ class DateAndTime : InjectingPreferenceFragment(), Preference.OnPreferenceChange
override fun getPreferenceXml() = R.xml.preferences_date_and_time override fun getPreferenceXml() = R.xml.preferences_date_and_time
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
val startOfWeekPreference: ListPreference = getStartOfWeekPreference() val startOfWeekPreference: ListPreference = getStartOfWeekPreference()
startOfWeekPreference.entries = getWeekdayEntries() startOfWeekPreference.entries = getWeekdayEntries()
startOfWeekPreference.onPreferenceChangeListener = this startOfWeekPreference.onPreferenceChangeListener = this

@ -23,7 +23,7 @@ class HelpAndFeedback : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.help_and_feedback override fun getPreferenceXml() = R.xml.help_and_feedback
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
val whatsNew = findPreference(R.string.whats_new) val whatsNew = findPreference(R.string.whats_new)
whatsNew.summary = getString(R.string.version_string, BuildConfig.VERSION_NAME) whatsNew.summary = getString(R.string.version_string, BuildConfig.VERSION_NAME)
whatsNew.setOnPreferenceClickListener { whatsNew.setOnPreferenceClickListener {

@ -71,7 +71,7 @@ class LookAndFeel : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences_look_and_feel override fun getPreferenceXml() = R.xml.preferences_look_and_feel
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
val themePref = findPreference(R.string.p_theme) val themePref = findPreference(R.string.p_theme)
val themeNames = resources.getStringArray(R.array.base_theme_names) val themeNames = resources.getStringArray(R.array.base_theme_names)
themePref.summary = themeNames[themeBase.index] themePref.summary = themeNames[themeBase.index]

@ -15,7 +15,7 @@ class MainSettingsFragment : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences override fun getPreferenceXml() = R.xml.preferences
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
requires(BuildConfig.DEBUG, R.string.debug) requires(BuildConfig.DEBUG, R.string.debug)
requires(appWidgetManager.widgetIds.isNotEmpty(), R.string.widget_settings) requires(appWidgetManager.widgetIds.isNotEmpty(), R.string.widget_settings)

@ -10,5 +10,5 @@ class NavigationDrawer : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences_navigation_drawer override fun getPreferenceXml() = R.xml.preferences_navigation_drawer
override fun setupPreferences(savedInstanceState: Bundle?) {} override suspend fun setupPreferences(savedInstanceState: Bundle?) {}
} }

@ -46,7 +46,7 @@ class Notifications : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences_notifications override fun getPreferenceXml() = R.xml.preferences_notifications
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
rescheduleNotificationsOnChange( rescheduleNotificationsOnChange(
false, false,
R.string.p_rmd_time, R.string.p_rmd_time,

@ -53,7 +53,7 @@ class ScrollableWidget : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences_widget override fun getPreferenceXml() = R.xml.preferences_widget
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
appWidgetId = requireArguments().getInt(EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID) appWidgetId = requireArguments().getInt(EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
widgetPreferences = WidgetPreferences(context, preferences, appWidgetId) widgetPreferences = WidgetPreferences(context, preferences, appWidgetId)

@ -3,6 +3,7 @@ package org.tasks.preferences.fragments
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceCategory import androidx.preference.PreferenceCategory
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
@ -10,14 +11,15 @@ import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity
import com.todoroo.astrid.service.TaskDeleter import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.tasks.R import org.tasks.R
import org.tasks.Strings.isNullOrEmpty import org.tasks.Strings.isNullOrEmpty
import org.tasks.caldav.CaldavAccountSettingsActivity import org.tasks.caldav.CaldavAccountSettingsActivity
import org.tasks.data.CaldavAccount import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.TYPE_LOCAL import org.tasks.data.CaldavAccount.Companion.TYPE_LOCAL
import org.tasks.data.CaldavDaoBlocking import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskAccount import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskListDaoBlocking import org.tasks.data.GoogleTaskListDao
import org.tasks.etesync.EteSyncAccountSettingsActivity import org.tasks.etesync.EteSyncAccountSettingsActivity
import org.tasks.injection.InjectingPreferenceFragment import org.tasks.injection.InjectingPreferenceFragment
import org.tasks.jobs.WorkManager import org.tasks.jobs.WorkManager
@ -35,21 +37,25 @@ class Synchronization : InjectingPreferenceFragment() {
@Inject lateinit var workManager: WorkManager @Inject lateinit var workManager: WorkManager
@Inject lateinit var preferences: Preferences @Inject lateinit var preferences: Preferences
@Inject lateinit var toaster: Toaster @Inject lateinit var toaster: Toaster
@Inject lateinit var caldavDao: CaldavDaoBlocking @Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var googleTaskListDao: GoogleTaskListDaoBlocking @Inject lateinit var googleTaskListDao: GoogleTaskListDao
@Inject lateinit var taskDeleter: TaskDeleter @Inject lateinit var taskDeleter: TaskDeleter
override fun getPreferenceXml() = R.xml.preferences_synchronization override fun getPreferenceXml() = R.xml.preferences_synchronization
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
findPreference(R.string.p_background_sync_unmetered_only) findPreference(R.string.p_background_sync_unmetered_only)
.setOnPreferenceChangeListener { _: Preference?, o: Any? -> .setOnPreferenceChangeListener { _: Preference?, o: Any? ->
workManager.updateBackgroundSync(null, null, o as Boolean?) lifecycleScope.launch {
workManager.updateBackgroundSync(null, null, o as Boolean?)
}
true true
} }
findPreference(R.string.p_background_sync) findPreference(R.string.p_background_sync)
.setOnPreferenceChangeListener { _: Preference?, o: Any? -> .setOnPreferenceChangeListener { _: Preference?, o: Any? ->
workManager.updateBackgroundSync(null, o as Boolean?, null) lifecycleScope.launch {
workManager.updateBackgroundSync(null, o as Boolean?, null)
}
true true
} }
@ -100,7 +106,7 @@ class Synchronization : InjectingPreferenceFragment() {
} }
} }
private fun addGoogleTasksAccounts(category: PreferenceCategory): Boolean { private suspend fun addGoogleTasksAccounts(category: PreferenceCategory): Boolean {
val accounts: List<GoogleTaskAccount> = googleTaskListDao.getAccounts() val accounts: List<GoogleTaskAccount> = googleTaskListDao.getAccounts()
for (googleTaskAccount in accounts) { for (googleTaskAccount in accounts) {
val account = googleTaskAccount.account val account = googleTaskAccount.account
@ -139,7 +145,7 @@ class Synchronization : InjectingPreferenceFragment() {
return accounts.isNotEmpty() return accounts.isNotEmpty()
} }
private fun addCaldavAccounts(category: PreferenceCategory): Boolean { private suspend fun addCaldavAccounts(category: PreferenceCategory): Boolean {
val accounts: List<CaldavAccount> = caldavDao.getAccounts().filter { val accounts: List<CaldavAccount> = caldavDao.getAccounts().filter {
it.accountType != TYPE_LOCAL it.accountType != TYPE_LOCAL
} }
@ -186,14 +192,15 @@ class Synchronization : InjectingPreferenceFragment() {
} }
private fun refresh() { private fun refresh() {
val synchronizationPreferences = findPreference(R.string.accounts) as PreferenceCategory lifecycleScope.launch {
synchronizationPreferences.removeAll() val synchronizationPreferences = findPreference(R.string.accounts) as PreferenceCategory
synchronizationPreferences.removeAll()
val hasGoogleAccounts: Boolean = addGoogleTasksAccounts(synchronizationPreferences) val hasGoogleAccounts: Boolean = addGoogleTasksAccounts(synchronizationPreferences)
val hasCaldavAccounts = addCaldavAccounts(synchronizationPreferences) val hasCaldavAccounts = addCaldavAccounts(synchronizationPreferences)
findPreference(R.string.gtasks_GPr_header).isVisible = hasGoogleAccounts findPreference(R.string.gtasks_GPr_header).isVisible = hasGoogleAccounts
val syncEnabled = hasGoogleAccounts || hasCaldavAccounts val syncEnabled = hasGoogleAccounts || hasCaldavAccounts
findPreference(R.string.accounts).isVisible = syncEnabled findPreference(R.string.accounts).isVisible = syncEnabled
findPreference(R.string.sync_SPr_interval_title).isVisible = syncEnabled findPreference(R.string.sync_SPr_interval_title).isVisible = syncEnabled
}
} }
} }

@ -34,7 +34,7 @@ class TaskDefaults : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences_task_defaults override fun getPreferenceXml() = R.xml.preferences_task_defaults
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
defaultCalendarPref = findPreference(R.string.gcal_p_default) defaultCalendarPref = findPreference(R.string.gcal_p_default)
defaultCalendarPref.onPreferenceClickListener = Preference.OnPreferenceClickListener { defaultCalendarPref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
newCalendarPicker(this, REQUEST_CALENDAR_SELECTION, getDefaultCalendarName()) newCalendarPicker(this, REQUEST_CALENDAR_SELECTION, getDefaultCalendarName())

@ -39,7 +39,7 @@ class TaskerListNotification : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences_tasker override fun getPreferenceXml() = R.xml.preferences_tasker
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {
filter = if (savedInstanceState == null) { filter = if (savedInstanceState == null) {
defaultFilterProvider.getFilterFromPreferenceBlocking(arguments?.getString(EXTRA_FILTER)) defaultFilterProvider.getFilterFromPreferenceBlocking(arguments?.getString(EXTRA_FILTER))
} else { } else {

@ -2,8 +2,10 @@ package org.tasks.preferences.fragments
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference import androidx.preference.Preference
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.tasks.R import org.tasks.R
import org.tasks.injection.InjectingPreferenceFragment import org.tasks.injection.InjectingPreferenceFragment
import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.DefaultFilterProvider
@ -22,27 +24,28 @@ class Widgets : InjectingPreferenceFragment() {
override fun getPreferenceXml() = R.xml.preferences_widgets override fun getPreferenceXml() = R.xml.preferences_widgets
override fun setupPreferences(savedInstanceState: Bundle?) { override suspend fun setupPreferences(savedInstanceState: Bundle?) {}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
preferenceScreen.removeAll() lifecycleScope.launch {
appWidgetManager.widgetIds.forEach { preferenceScreen.removeAll()
val widgetPrefs = WidgetPreferences(context, preferences, it) appWidgetManager.widgetIds.forEach {
val pref = Preference(context) val widgetPrefs = WidgetPreferences(context, preferences, it)
val filter = defaultFilterProvider.getFilterFromPreferenceBlocking(widgetPrefs.filterId) val pref = Preference(context)
pref.title = filter?.listingTitle val filter = defaultFilterProvider.getFilterFromPreference(widgetPrefs.filterId)
pref.summary = getString(R.string.widget_id, it) pref.title = filter.listingTitle
val intent = Intent(context, WidgetConfigActivity::class.java) pref.summary = getString(R.string.widget_id, it)
intent.putExtra(android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID, it) val intent = Intent(context, WidgetConfigActivity::class.java)
intent.action = "widget_settings" intent.putExtra(android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID, it)
pref.setOnPreferenceClickListener { intent.action = "widget_settings"
startActivity(intent) pref.setOnPreferenceClickListener {
false startActivity(intent)
false
}
preferenceScreen.addPreference(pref)
} }
preferenceScreen.addPreference(pref)
} }
} }
} }
Loading…
Cancel
Save