Reorganize settings

This commit is contained in:
2023-02-10 21:44:45 +02:00
parent 2acbff487e
commit ea9ae2263c
13 changed files with 228 additions and 182 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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