Merge pull request #457 from Bnyro/master

move instance settings to its own fragment
This commit is contained in:
Bnyro 2022-06-11 15:55:45 +02:00 committed by GitHub
commit 2acbbcc02e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 340 additions and 301 deletions

View File

@ -0,0 +1,266 @@
package com.github.libretube.preferences
import android.Manifest
import android.content.ContentResolver
import android.content.Context
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.github.libretube.R
import com.github.libretube.dialogs.LoginDialog
import com.github.libretube.requireMainActivityRestart
import com.github.libretube.util.RetrofitInstance
import java.io.IOException
import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import org.json.JSONObject
import org.json.JSONTokener
import retrofit2.HttpException
class InstanceSettings : PreferenceFragmentCompat() {
val TAG = "InstanceSettings"
override fun onCreate(savedInstanceState: Bundle?) {
MainSettings.getContent =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
if (uri != null) {
try {
// Open a specific media item using ParcelFileDescriptor.
val resolver: ContentResolver =
requireActivity()
.contentResolver
// "rw" for read-and-write;
// "rwt" for truncating or overwriting existing file contents.
// val readOnlyMode = "r"
// uri - I have got from onActivityResult
val type = resolver.getType(uri)
var inputStream: InputStream? = resolver.openInputStream(uri)
val channels = ArrayList<String>()
if (type == "application/json") {
val json = inputStream?.bufferedReader()?.readLines()?.get(0)
val jsonObject = JSONTokener(json).nextValue() as JSONObject
Log.e(TAG, jsonObject.getJSONArray("subscriptions").toString())
for (
i in 0 until jsonObject.getJSONArray("subscriptions")
.length()
) {
var url =
jsonObject.getJSONArray("subscriptions").getJSONObject(i)
.getString("url")
url = url.replace("https://www.youtube.com/channel/", "")
Log.e(TAG, url)
channels.add(url)
}
} else {
if (type == "application/zip") {
val zis = ZipInputStream(inputStream)
var entry: ZipEntry? = zis.nextEntry
while (entry != null) {
if (entry.name.endsWith(".csv")) {
inputStream = zis
break
}
entry = zis.nextEntry
}
}
inputStream?.bufferedReader()?.readLines()?.forEach {
if (it.isNotBlank()) {
val channelId = it.substringBefore(",")
if (channelId.length == 24)
channels.add(channelId)
}
}
}
inputStream?.close()
subscribe(channels)
} catch (e: Exception) {
Log.e(TAG, e.toString())
Toast.makeText(
context,
R.string.error,
Toast.LENGTH_SHORT
).show()
}
}
}
super.onCreate(savedInstanceState)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.instance_settings, rootKey)
val topBarTextView = activity?.findViewById<TextView>(R.id.topBar_textView)
topBarTextView?.text = getString(R.string.instance)
val instance = findPreference<ListPreference>("instance_server")
fetchInstance()
instance?.setOnPreferenceChangeListener { _, newValue ->
RetrofitInstance.url = newValue.toString()
RetrofitInstance.lazyMgr.reset()
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
if (sharedPref?.getString("token", "") != "") {
with(sharedPref!!.edit()) {
putString("token", "")
apply()
}
Toast.makeText(context, R.string.loggedout, Toast.LENGTH_SHORT).show()
}
true
}
val login = findPreference<Preference>("login_register")
login?.setOnPreferenceClickListener {
requireMainActivityRestart = true
val newFragment = LoginDialog()
newFragment.show(childFragmentManager, "Login")
true
}
val importFromYt = findPreference<Preference>("import_from_yt")
importFromYt?.setOnPreferenceClickListener {
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
val token = sharedPref?.getString("token", "")!!
// check StorageAccess
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Log.d("myz", "" + Build.VERSION.SDK_INT)
if (ContextCompat.checkSelfPermission(
this.requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this.requireActivity(),
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE
),
1
) // permission request code is just an int
} else if (token != "") {
MainSettings.getContent.launch("*/*")
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
} else {
if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this.requireActivity(),
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
1
)
} else if (token != "") {
MainSettings.getContent.launch("*/*")
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
}
true
}
}
private fun fetchInstance() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.getInstances("https://instances.tokhmi.xyz/")
} catch (e: IOException) {
println(e)
Log.e("settings", "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
Log.e("settings", "HttpException, unexpected response $e")
return@launchWhenCreated
} catch (e: Exception) {
Log.e("settings", e.toString())
return@launchWhenCreated
}
val listEntries: MutableList<String> = ArrayList()
val listEntryValues: MutableList<String> = ArrayList()
for (item in response) {
listEntries.add(item.name!!)
listEntryValues.add(item.api_url!!)
}
val entries = listEntries.toTypedArray<CharSequence>()
val entryValues = listEntryValues.toTypedArray<CharSequence>()
runOnUiThread {
val instance = findPreference<ListPreference>("instance_server")
instance?.entries = entries
instance?.entryValues = entryValues
instance?.summaryProvider =
Preference.SummaryProvider<ListPreference> { preference ->
val text = preference.entry
if (TextUtils.isEmpty(text)) {
"kavin.rocks (Official)"
} else {
text
}
}
}
}
}
private fun Fragment?.runOnUiThread(action: () -> Unit) {
this ?: return
if (!isAdded) return // Fragment not attached to an Activity
activity?.runOnUiThread(action)
}
private fun subscribe(channels: List<String>) {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
val sharedPref =
context?.getSharedPreferences("token", Context.MODE_PRIVATE)
RetrofitInstance.api.importSubscriptions(
false,
sharedPref?.getString("token", "")!!,
channels
)
} catch (e: IOException) {
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response$e")
return@launchWhenCreated
}
if (response.message == "ok") {
Toast.makeText(
context,
R.string.importsuccess,
Toast.LENGTH_SHORT
).show()
}
}
}
run()
}
}

View File

@ -1,37 +1,15 @@
package com.github.libretube.preferences package com.github.libretube.preferences
import android.Manifest
import android.content.ContentResolver
import android.content.Context
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.dialogs.LoginDialog
import com.github.libretube.isCurrentViewMainSettings import com.github.libretube.isCurrentViewMainSettings
import com.github.libretube.requireMainActivityRestart import com.github.libretube.requireMainActivityRestart
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper import com.github.libretube.util.ThemeHelper
import java.io.IOException
import java.io.InputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import org.json.JSONObject
import org.json.JSONTokener
import retrofit2.HttpException
class MainSettings : PreferenceFragmentCompat() { class MainSettings : PreferenceFragmentCompat() {
val TAG = "SettingsFragment" val TAG = "SettingsFragment"
@ -40,76 +18,6 @@ class MainSettings : PreferenceFragmentCompat() {
lateinit var getContent: ActivityResultLauncher<String> lateinit var getContent: ActivityResultLauncher<String>
} }
override fun onCreate(savedInstanceState: Bundle?) {
getContent =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
if (uri != null) {
try {
// Open a specific media item using ParcelFileDescriptor.
val resolver: ContentResolver =
requireActivity()
.contentResolver
// "rw" for read-and-write;
// "rwt" for truncating or overwriting existing file contents.
// val readOnlyMode = "r"
// uri - I have got from onActivityResult
val type = resolver.getType(uri)
var inputStream: InputStream? = resolver.openInputStream(uri)
val channels = ArrayList<String>()
if (type == "application/json") {
val json = inputStream?.bufferedReader()?.readLines()?.get(0)
val jsonObject = JSONTokener(json).nextValue() as JSONObject
Log.e(TAG, jsonObject.getJSONArray("subscriptions").toString())
for (
i in 0 until jsonObject.getJSONArray("subscriptions")
.length()
) {
var url =
jsonObject.getJSONArray("subscriptions").getJSONObject(i)
.getString("url")
url = url.replace("https://www.youtube.com/channel/", "")
Log.e(TAG, url)
channels.add(url)
}
} else {
if (type == "application/zip") {
val zis = ZipInputStream(inputStream)
var entry: ZipEntry? = zis.nextEntry
while (entry != null) {
if (entry.name.endsWith(".csv")) {
inputStream = zis
break
}
entry = zis.nextEntry
}
}
inputStream?.bufferedReader()?.readLines()?.forEach {
if (it.isNotBlank()) {
val channelId = it.substringBefore(",")
if (channelId.length == 24)
channels.add(channelId)
}
}
}
inputStream?.close()
subscribe(channels)
} catch (e: Exception) {
Log.e(TAG, e.toString())
Toast.makeText(
context,
R.string.error,
Toast.LENGTH_SHORT
).show()
}
}
}
super.onCreate(savedInstanceState)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings, rootKey) setPreferencesFromResource(R.xml.settings, rootKey)
@ -125,27 +33,10 @@ class MainSettings : PreferenceFragmentCompat() {
true true
} }
val instance = findPreference<ListPreference>("instance") val instance = findPreference<Preference>("instance")
fetchInstance() instance?.setOnPreferenceClickListener {
instance?.setOnPreferenceChangeListener { _, newValue -> val newFragment = InstanceSettings()
RetrofitInstance.url = newValue.toString() navigateSettings(newFragment)
RetrofitInstance.lazyMgr.reset()
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
if (sharedPref?.getString("token", "") != "") {
with(sharedPref!!.edit()) {
putString("token", "")
apply()
}
Toast.makeText(context, R.string.loggedout, Toast.LENGTH_SHORT).show()
}
true
}
val login = findPreference<Preference>("login_register")
login?.setOnPreferenceClickListener {
requireMainActivityRestart = true
val newFragment = LoginDialog()
newFragment.show(childFragmentManager, "Login")
true true
} }
@ -156,8 +47,8 @@ class MainSettings : PreferenceFragmentCompat() {
true true
} }
val sponsorblock = findPreference<Preference>("sponsorblock") val sponsorBlock = findPreference<Preference>("sponsorblock")
sponsorblock?.setOnPreferenceClickListener { sponsorBlock?.setOnPreferenceClickListener {
val newFragment = SponsorBlockSettings() val newFragment = SponsorBlockSettings()
navigateSettings(newFragment) navigateSettings(newFragment)
true true
@ -176,59 +67,6 @@ class MainSettings : PreferenceFragmentCompat() {
navigateSettings(newFragment) navigateSettings(newFragment)
true true
} }
val importFromYt = findPreference<Preference>("import_from_yt")
importFromYt?.setOnPreferenceClickListener {
val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE)
val token = sharedPref?.getString("token", "")!!
// check StorageAccess
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Log.d("myz", "" + Build.VERSION.SDK_INT)
if (ContextCompat.checkSelfPermission(
this.requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this.requireActivity(),
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE
),
1
) // permission request code is just an int
} else if (token != "") {
getContent.launch("*/*")
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
} else {
if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED ||
ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this.requireActivity(),
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
1
)
} else if (token != "") {
getContent.launch("*/*")
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
}
true
}
} }
private fun navigateSettings(newFragment: Fragment) { private fun navigateSettings(newFragment: Fragment) {
@ -237,80 +75,4 @@ class MainSettings : PreferenceFragmentCompat() {
.replace(R.id.settings, newFragment) .replace(R.id.settings, newFragment)
.commitNow() .commitNow()
} }
private fun fetchInstance() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.api.getInstances("https://instances.tokhmi.xyz/")
} catch (e: IOException) {
println(e)
Log.e("settings", "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
Log.e("settings", "HttpException, unexpected response $e")
return@launchWhenCreated
} catch (e: Exception) {
Log.e("settings", e.toString())
return@launchWhenCreated
}
val listEntries: MutableList<String> = ArrayList()
val listEntryValues: MutableList<String> = ArrayList()
for (item in response) {
listEntries.add(item.name!!)
listEntryValues.add(item.api_url!!)
}
val entries = listEntries.toTypedArray<CharSequence>()
val entryValues = listEntryValues.toTypedArray<CharSequence>()
runOnUiThread {
val instance = findPreference<ListPreference>("instance")
instance?.entries = entries
instance?.entryValues = entryValues
instance?.summaryProvider =
Preference.SummaryProvider<ListPreference> { preference ->
val text = preference.entry
if (TextUtils.isEmpty(text)) {
"kavin.rocks (Official)"
} else {
text
}
}
}
}
}
private fun Fragment?.runOnUiThread(action: () -> Unit) {
this ?: return
if (!isAdded) return // Fragment not attached to an Activity
activity?.runOnUiThread(action)
}
private fun subscribe(channels: List<String>) {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
val sharedPref =
context?.getSharedPreferences("token", Context.MODE_PRIVATE)
RetrofitInstance.api.importSubscriptions(
false,
sharedPref?.getString("token", "")!!,
channels
)
} catch (e: IOException) {
Log.e(TAG, "IOException, you might not have internet connection")
return@launchWhenCreated
} catch (e: HttpException) {
Log.e(TAG, "HttpException, unexpected response$e")
return@launchWhenCreated
}
if (response.message == "ok") {
Toast.makeText(
context,
R.string.importsuccess,
Toast.LENGTH_SHORT
).show()
}
}
}
run()
}
} }

View File

@ -39,7 +39,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="10dp"> android:paddingHorizontal="20dp" android:paddingVertical="10dp">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -59,7 +59,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="10dp"> android:paddingHorizontal="20dp" android:paddingVertical="10dp">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -79,7 +79,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="10dp"> android:paddingHorizontal="20dp" android:paddingVertical="10dp">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -99,7 +99,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="10dp"> android:paddingHorizontal="20dp" android:paddingVertical="10dp">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -119,7 +119,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="10dp"> android:paddingHorizontal="20dp" android:paddingVertical="10dp">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -139,7 +139,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="10dp"> android:paddingHorizontal="20dp" android:paddingVertical="10dp">
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -25,7 +25,7 @@
<string name="login_first">Please log in and try again.</string> <string name="login_first">Please log in and try again.</string>
<string name="instances">Choose an instance</string> <string name="instances">Choose an instance</string>
<string name="customInstance">Add a custom instance</string> <string name="customInstance">Add a custom instance</string>
<string name="region">Choose a region</string> <string name="region">Region</string>
<string name="login_register">Log in/register</string> <string name="login_register">Log in/register</string>
<string name="please_login">Please log in or register in the settings first.</string> <string name="please_login">Please log in or register in the settings first.</string>
<string name="importsuccess">Subscribed</string> <string name="importsuccess">Subscribed</string>
@ -161,4 +161,5 @@
<string name="shapedIcon">Silly shaped</string> <string name="shapedIcon">Silly shaped</string>
<string name="flameIcon">Flying flame</string> <string name="flameIcon">Flying flame</string>
<string name="birdIcon">Boosted bird</string> <string name="birdIcon">Boosted bird</string>
<string name="instance_summary">Piped, login, subscriptions</string>
</resources> </resources>

View File

@ -0,0 +1,34 @@
<?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">
<PreferenceCategory app:title="@string/instance">
<ListPreference
android:icon="@drawable/ic_server"
app:defaultValue="https://pipedapi.kavin.rocks/"
app:entries="@array/instances"
app:entryValues="@array/instancesValue"
app:key="instance_server"
app:title="@string/instances" />
<EditTextPreference
app:isPreferenceVisible="false"
app:key="customInstance"
app:title="@string/customInstance" />
<Preference
android:icon="@drawable/ic_login"
android:summary="@string/notgmail"
app:key="login_register"
app:title="@string/login_register" />
<Preference
android:icon="@drawable/ic_upload"
android:summary="@string/import_from_yt_summary"
app:key="import_from_yt"
app:title="@string/import_from_yt" />
</PreferenceCategory>
</PreferenceScreen>

View File

@ -5,83 +5,59 @@
<PreferenceCategory app:title="@string/location"> <PreferenceCategory app:title="@string/location">
<ListPreference <ListPreference
app:key="region" android:icon="@drawable/ic_region"
app:title="@string/region" app:defaultValue="US"
app:entries="@array/regions" app:entries="@array/regions"
app:entryValues="@array/regionsValue" app:entryValues="@array/regionsValue"
app:defaultValue="US" app:key="region"
app:useSimpleSummaryProvider="true" app:title="@string/region"
android:icon="@drawable/ic_region" /> app:useSimpleSummaryProvider="true" />
<ListPreference <ListPreference
app:key="language" android:icon="@drawable/ic_translate"
app:title="@string/changeLanguage" app:defaultValue="sys"
app:entries="@array/languages" app:entries="@array/languages"
app:entryValues="@array/languagesValue" app:entryValues="@array/languagesValue"
app:defaultValue="sys" app:key="language"
app:useSimpleSummaryProvider="true" app:title="@string/changeLanguage"
android:icon="@drawable/ic_translate" /> app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/instance">
<ListPreference
app:key="instance"
app:title="@string/instances"
app:entries="@array/instances"
app:entryValues="@array/instancesValue"
app:defaultValue="https://pipedapi.kavin.rocks/"
android:icon="@drawable/ic_server" />
<EditTextPreference
app:key="customInstance"
app:title="@string/customInstance"
app:isPreferenceVisible="false" />
<Preference
app:key="login_register"
app:title="@string/login_register"
android:icon="@drawable/ic_login"
android:summary="@string/notgmail" />
<Preference
app:key="import_from_yt"
app:title="@string/import_from_yt"
android:summary="@string/import_from_yt_summary"
android:icon="@drawable/ic_upload" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory app:title="@string/customization"> <PreferenceCategory app:title="@string/customization">
<Preference <Preference
app:key="appearance" android:icon="@drawable/ic_server"
app:title="@string/appearance" app:key="instance"
app:summary="@string/appearance_summary" app:summary="@string/instance_summary"
android:icon="@drawable/ic_color" /> app:title="@string/instance" />
<Preference <Preference
app:title="@string/sponsorblock" android:icon="@drawable/ic_color"
app:key="appearance"
app:summary="@string/appearance_summary"
app:title="@string/appearance" />
<Preference
android:icon="@drawable/ic_block"
app:key="sponsorblock" app:key="sponsorblock"
app:summary="@string/sponsorblock_summary" app:summary="@string/sponsorblock_summary"
android:icon="@drawable/ic_block" /> app:title="@string/sponsorblock" />
<Preference <Preference
android:icon="@drawable/ic_list"
app:key="advanced" app:key="advanced"
app:title="@string/advanced"
app:summary="@string/advanced_summary" app:summary="@string/advanced_summary"
android:icon="@drawable/ic_list" /> app:title="@string/advanced" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory> <PreferenceCategory>
<Preference <Preference
app:title="@string/about" android:icon="@drawable/ic_info"
app:key="about" app:key="about"
android:icon="@drawable/ic_info" /> app:title="@string/about" />
</PreferenceCategory> </PreferenceCategory>