Reorganize settings

pull/299/head
Koitharu 3 years ago
parent 2acbff487e
commit ea9ae2263c
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -94,7 +94,7 @@ dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.10' implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.10'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation "androidx.appcompat:appcompat:1.6.0" implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.activity:activity-ktx:1.6.1' implementation 'androidx.activity:activity-ktx:1.6.1'
implementation 'androidx.fragment:fragment-ktx:1.5.5' implementation 'androidx.fragment:fragment-ktx:1.5.5'

@ -1,17 +1,12 @@
package org.koitharu.kotatsu.settings package org.koitharu.kotatsu.settings
import android.accounts.AccountManager
import android.content.ActivityNotFoundException
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.base.ui.dialog.StorageSelectDialog import org.koitharu.kotatsu.base.ui.dialog.StorageSelectDialog
@ -21,7 +16,6 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.local.data.LocalStorageManager import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.parsers.util.names import org.koitharu.kotatsu.parsers.util.names
import org.koitharu.kotatsu.settings.utils.SliderPreference import org.koitharu.kotatsu.settings.utils.SliderPreference
import org.koitharu.kotatsu.sync.ui.SyncSettingsIntent
import org.koitharu.kotatsu.utils.ext.getStorageName import org.koitharu.kotatsu.utils.ext.getStorageName
import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
@ -71,11 +65,6 @@ class ContentSettingsFragment :
settings.subscribe(this) settings.subscribe(this)
} }
override fun onResume() {
super.onResume()
bindSyncSummary()
}
override fun onDestroyView() { override fun onDestroyView() {
settings.unsubscribe(this) settings.unsubscribe(this)
super.onDestroyView() super.onDestroyView()
@ -111,22 +100,6 @@ class ContentSettingsFragment :
true true
} }
AppSettings.KEY_SYNC -> {
val am = AccountManager.get(requireContext())
val accountType = getString(R.string.account_type_sync)
val account = am.getAccountsByType(accountType).firstOrNull()
if (account == null) {
am.addAccount(accountType, accountType, null, null, requireActivity(), null, null)
} else {
try {
startActivity(SyncSettingsIntent(account))
} catch (_: ActivityNotFoundException) {
Snackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
}
}
true
}
else -> super.onPreferenceTreeClick(preference) else -> super.onPreferenceTreeClick(preference)
} }
} }
@ -148,16 +121,4 @@ class ContentSettingsFragment :
summary = getString(R.string.enabled_d_of_d, total - settings.hiddenSources.size, total) summary = getString(R.string.enabled_d_of_d, total - settings.hiddenSources.size, total)
} }
} }
private fun bindSyncSummary() {
viewLifecycleScope.launch {
val account = withContext(Dispatchers.Default) {
val type = getString(R.string.account_type_sync)
AccountManager.get(requireContext()).getAccountsByType(type).firstOrNull()
}
findPreference<Preference>(AppSettings.KEY_SYNC)?.run {
summary = account?.name ?: getString(R.string.sync_title)
}
}
}
} }

@ -1,7 +1,5 @@
package org.koitharu.kotatsu.settings package org.koitharu.kotatsu.settings
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.preference.Preference import androidx.preference.Preference
@ -9,9 +7,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
@ -19,16 +15,10 @@ import org.koitharu.kotatsu.core.os.ShortcutsUpdater
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.local.data.CacheDir import org.koitharu.kotatsu.local.data.CacheDir
import org.koitharu.kotatsu.local.data.LocalStorageManager import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.ui.config.ScrobblerConfigActivity
import org.koitharu.kotatsu.scrobbling.mal.data.MALRepository
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository
import org.koitharu.kotatsu.search.domain.MangaSearchRepository import org.koitharu.kotatsu.search.domain.MangaSearchRepository
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.FileSize import org.koitharu.kotatsu.utils.FileSize
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
import javax.inject.Inject import javax.inject.Inject
@ -44,15 +34,6 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
@Inject @Inject
lateinit var storageManager: LocalStorageManager lateinit var storageManager: LocalStorageManager
@Inject
lateinit var shikimoriRepository: ShikimoriRepository
@Inject
lateinit var aniListRepository: AniListRepository
@Inject
lateinit var malRepository: MALRepository
@Inject @Inject
lateinit var cookieJar: MutableCookieJar lateinit var cookieJar: MutableCookieJar
@ -85,13 +66,6 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
} }
} }
override fun onResume() {
super.onResume()
bindScrobblerSummary(AppSettings.KEY_SHIKIMORI, shikimoriRepository)
bindScrobblerSummary(AppSettings.KEY_ANILIST, aniListRepository)
bindScrobblerSummary(AppSettings.KEY_MAL, malRepository)
}
override fun onPreferenceTreeClick(preference: Preference): Boolean { override fun onPreferenceTreeClick(preference: Preference): Boolean {
return when (preference.key) { return when (preference.key) {
AppSettings.KEY_PAGES_CACHE_CLEAR -> { AppSettings.KEY_PAGES_CACHE_CLEAR -> {
@ -128,33 +102,6 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
true true
} }
AppSettings.KEY_SHIKIMORI -> {
if (!shikimoriRepository.isAuthorized) {
launchScrobblerAuth(shikimoriRepository)
} else {
startActivity(ScrobblerConfigActivity.newIntent(preference.context, ScrobblerService.SHIKIMORI))
}
true
}
AppSettings.KEY_MAL -> {
if (!malRepository.isAuthorized) {
launchScrobblerAuth(malRepository)
} else {
startActivity(ScrobblerConfigActivity.newIntent(preference.context, ScrobblerService.MAL))
}
true
}
AppSettings.KEY_ANILIST -> {
if (!aniListRepository.isAuthorized) {
launchScrobblerAuth(aniListRepository)
} else {
startActivity(ScrobblerConfigActivity.newIntent(preference.context, ScrobblerService.ANILIST))
}
true
}
else -> super.onPreferenceTreeClick(preference) else -> super.onPreferenceTreeClick(preference)
} }
} }
@ -217,42 +164,4 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
} }
}.show() }.show()
} }
private fun bindScrobblerSummary(
key: String,
repository: org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository
) {
val pref = findPreference<Preference>(key) ?: return
if (!repository.isAuthorized) {
pref.setSummary(R.string.disabled)
return
}
val username = repository.cachedUser?.nickname
if (username != null) {
pref.summary = getString(R.string.logged_in_as, username)
} else {
pref.setSummary(R.string.loading_)
viewLifecycleScope.launch {
pref.summary = withContext(Dispatchers.Default) {
runCatching {
val user = repository.loadUser()
getString(R.string.logged_in_as, user.nickname)
}.getOrElse {
it.printStackTraceDebug()
it.getDisplayMessage(resources)
}
}
}
}
}
private fun launchScrobblerAuth(repository: org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository) {
runCatching {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(repository.oauthUrl)
startActivity(intent)
}.onFailure {
Snackbar.make(listView, it.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
}
}
} }

@ -0,0 +1,150 @@
package org.koitharu.kotatsu.settings
import android.accounts.AccountManager
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.common.ui.config.ScrobblerConfigActivity
import org.koitharu.kotatsu.scrobbling.mal.data.MALRepository
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository
import org.koitharu.kotatsu.sync.ui.SyncSettingsIntent
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
import javax.inject.Inject
@AndroidEntryPoint
class ServicesSettingsFragment : BasePreferenceFragment(R.string.services) {
@Inject
lateinit var shikimoriRepository: ShikimoriRepository
@Inject
lateinit var aniListRepository: AniListRepository
@Inject
lateinit var malRepository: MALRepository
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_services)
}
override fun onResume() {
super.onResume()
bindScrobblerSummary(AppSettings.KEY_SHIKIMORI, shikimoriRepository)
bindScrobblerSummary(AppSettings.KEY_ANILIST, aniListRepository)
bindScrobblerSummary(AppSettings.KEY_MAL, malRepository)
bindSyncSummary()
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
return when (preference.key) {
AppSettings.KEY_SHIKIMORI -> {
if (!shikimoriRepository.isAuthorized) {
launchScrobblerAuth(shikimoriRepository)
} else {
startActivity(ScrobblerConfigActivity.newIntent(preference.context, ScrobblerService.SHIKIMORI))
}
true
}
AppSettings.KEY_MAL -> {
if (!malRepository.isAuthorized) {
launchScrobblerAuth(malRepository)
} else {
startActivity(ScrobblerConfigActivity.newIntent(preference.context, ScrobblerService.MAL))
}
true
}
AppSettings.KEY_ANILIST -> {
if (!aniListRepository.isAuthorized) {
launchScrobblerAuth(aniListRepository)
} else {
startActivity(ScrobblerConfigActivity.newIntent(preference.context, ScrobblerService.ANILIST))
}
true
}
AppSettings.KEY_SYNC -> {
val am = AccountManager.get(requireContext())
val accountType = getString(R.string.account_type_sync)
val account = am.getAccountsByType(accountType).firstOrNull()
if (account == null) {
am.addAccount(accountType, accountType, null, null, requireActivity(), null, null)
} else {
try {
startActivity(SyncSettingsIntent(account))
} catch (_: ActivityNotFoundException) {
Snackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
}
}
true
}
else -> super.onPreferenceTreeClick(preference)
}
}
private fun bindScrobblerSummary(
key: String,
repository: org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository
) {
val pref = findPreference<Preference>(key) ?: return
if (!repository.isAuthorized) {
pref.setSummary(R.string.disabled)
return
}
val username = repository.cachedUser?.nickname
if (username != null) {
pref.summary = getString(R.string.logged_in_as, username)
} else {
pref.setSummary(R.string.loading_)
viewLifecycleScope.launch {
pref.summary = withContext(Dispatchers.Default) {
runCatching {
val user = repository.loadUser()
getString(R.string.logged_in_as, user.nickname)
}.getOrElse {
it.printStackTraceDebug()
it.getDisplayMessage(resources)
}
}
}
}
}
private fun launchScrobblerAuth(repository: org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository) {
runCatching {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(repository.oauthUrl)
startActivity(intent)
}.onFailure {
Snackbar.make(listView, it.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
}
}
private fun bindSyncSummary() {
viewLifecycleScope.launch {
val account = withContext(Dispatchers.Default) {
val type = getString(R.string.account_type_sync)
AccountManager.get(requireContext()).getAccountsByType(type).firstOrNull()
}
findPreference<Preference>(AppSettings.KEY_SYNC)?.run {
summary = account?.name ?: getString(R.string.sync_title)
}
}
}
}

@ -0,0 +1,11 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M20 8H17V6C17 4.9 16.1 4 15 4H9C7.9 4 7 4.9 7 6V8H4C2.9 8 2 8.9 2 10V20H22V10C22 8.9 21.1 8 20 8M9 6H15V8H9V6M20 18H4V15H6V16H8V15H16V16H18V15H20V18M18 13V12H16V13H8V12H6V13H4V10H20V13H18Z" />
</vector>

@ -13,9 +13,8 @@
<com.google.android.material.appbar.CollapsingToolbarLayout <com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbarLayout" android:id="@+id/collapsingToolbarLayout"
style="?attr/collapsingToolbarLayoutLargeStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/collapsingToolbarLayoutLargeSize" android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:toolbarId="@id/toolbar"> app:toolbarId="@id/toolbar">

@ -418,4 +418,5 @@
<string name="theme_name_sakura">Sakura</string> <string name="theme_name_sakura">Sakura</string>
<string name="nothing_here">There is nothing here</string> <string name="nothing_here">There is nothing here</string>
<string name="scrobbling_empty_hint">To track reading progress, select Menu → Track on the manga details screen.</string> <string name="scrobbling_empty_hint">To track reading progress, select Menu → Track on the manga details screen.</string>
<string name="services">Services</string>
</resources> </resources>

@ -73,6 +73,7 @@
<item name="listItemTextViewStyle">@style/Widget.Kotatsu.ListItemTextView</item> <item name="listItemTextViewStyle">@style/Widget.Kotatsu.ListItemTextView</item>
<item name="materialSwitchStyle">@style/Widget.Material3.CompoundButton.MaterialSwitch</item> <item name="materialSwitchStyle">@style/Widget.Material3.CompoundButton.MaterialSwitch</item>
<item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat.M3</item> <item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat.M3</item>
<item name="collapsingToolbarLayoutStyle">@style/Widget.Material3.CollapsingToolbar.Medium</item>
<!-- Text appearance --> <!-- Text appearance -->
<item name="actionMenuTextAppearance">@style/TextAppearance.Kotatsu.Menu</item> <item name="actionMenuTextAppearance">@style/TextAppearance.Kotatsu.Menu</item>

@ -22,25 +22,35 @@
android:summary="@string/black_dark_theme_summary" android:summary="@string/black_dark_theme_summary"
android:title="@string/black_dark_theme" /> android:title="@string/black_dark_theme" />
<PreferenceCategory android:title="Manga list">
<ListPreference
android:entries="@array/list_modes"
android:key="list_mode_2"
android:title="@string/list_mode"
app:useSimpleSummaryProvider="true" />
<org.koitharu.kotatsu.settings.utils.SliderPreference
android:key="grid_size"
android:stepSize="5"
android:title="@string/grid_size"
android:valueFrom="50"
android:valueTo="150"
app:defaultValue="100" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="reading_indicators"
android:summary="@string/show_reading_indicators_summary"
android:title="@string/show_reading_indicators" />
</PreferenceCategory>
<org.koitharu.kotatsu.settings.utils.ActivityListPreference <org.koitharu.kotatsu.settings.utils.ActivityListPreference
android:key="app_locale" android:key="app_locale"
android:title="@string/language" android:title="@string/language"
app:allowDividerAbove="true" /> app:allowDividerAbove="true" />
<ListPreference
android:entries="@array/list_modes"
android:key="list_mode_2"
android:title="@string/list_mode"
app:useSimpleSummaryProvider="true" />
<org.koitharu.kotatsu.settings.utils.SliderPreference
android:key="grid_size"
android:stepSize="5"
android:title="@string/grid_size"
android:valueFrom="50"
android:valueTo="150"
app:defaultValue="100" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"
android:key="exit_confirm" android:key="exit_confirm"

@ -56,15 +56,9 @@
android:valueTo="5" android:valueTo="5"
app:defaultValue="2" /> app:defaultValue="2" />
<Preference
android:key="sync"
android:persistent="false"
android:summary="@string/sync_title"
android:title="@string/sync"
app:allowDividerAbove="true" />
<PreferenceScreen <PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.backup.BackupSettingsFragment" android:fragment="org.koitharu.kotatsu.settings.backup.BackupSettingsFragment"
android:title="@string/backup_restore" /> android:title="@string/backup_restore"
app:allowDividerAbove="true" />
</PreferenceScreen> </PreferenceScreen>

@ -1,13 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen <PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="reading_indicators"
android:summary="@string/show_reading_indicators_summary"
android:title="@string/show_reading_indicators" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:key="history_exclude_nsfw" android:key="history_exclude_nsfw"
@ -20,25 +13,6 @@
android:summary="@string/history_shortcuts_summary" android:summary="@string/history_shortcuts_summary"
android:title="@string/history_shortcuts" /> android:title="@string/history_shortcuts" />
<PreferenceCategory android:title="@string/tracking">
<Preference
android:key="shikimori"
android:title="@string/shikimori"
app:icon="@drawable/ic_shikimori" />
<Preference
android:key="anilist"
android:title="@string/anilist"
app:icon="@drawable/ic_anilist" />
<Preference
android:key="mal"
android:title="@string/mal"
app:icon="@drawable/ic_mal" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/data_deletion"> <PreferenceCategory android:title="@string/data_deletion">
<Preference <Preference

@ -24,6 +24,11 @@
android:icon="@drawable/ic_book_page" android:icon="@drawable/ic_book_page"
android:title="@string/reader_settings" /> android:title="@string/reader_settings" />
<PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.ServicesSettingsFragment"
android:icon="@drawable/ic_services"
android:title="@string/services" />
<PreferenceScreen <PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment" android:fragment="org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment"
android:icon="@drawable/ic_feed" android:icon="@drawable/ic_feed"

@ -0,0 +1,31 @@
<?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">
<Preference
android:key="sync"
android:persistent="false"
android:summary="@string/sync_title"
android:title="@string/sync" />
<PreferenceCategory android:title="@string/tracking">
<Preference
android:key="shikimori"
android:title="@string/shikimori"
app:icon="@drawable/ic_shikimori" />
<Preference
android:key="anilist"
android:title="@string/anilist"
app:icon="@drawable/ic_anilist" />
<Preference
android:key="mal"
android:title="@string/mal"
app:icon="@drawable/ic_mal" />
</PreferenceCategory>
</PreferenceScreen>
Loading…
Cancel
Save