Merge pull request #413 from Bnyro/master

Settings Refactor and code formatting
This commit is contained in:
Bnyro 2022-06-07 09:56:12 +02:00 committed by GitHub
commit 533f69637b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1335 additions and 1225 deletions

View File

@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@ -18,14 +19,12 @@
android:name=".MyApp"
android:networkSecurityConfig="@xml/network_security_config"
android:largeHeap="true"
android:requestLegacyExternalStorage="true"
>
android:requestLegacyExternalStorage="true">
<activity
android:name=".Player"
android:exported="false"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:configChanges="orientation|screenSize"
/>
android:configChanges="orientation|screenSize" />
<activity
android:name=".SettingsActivity"
android:label="@string/settings" />
@ -35,8 +34,7 @@
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:hardwareAccelerated="true"
android:screenOrientation="userPortrait"
>
android:screenOrientation="userPortrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -165,8 +163,7 @@
<activity
android:name=".RouterActivity"
android:exported="true"
android:launchMode="singleInstance"
>
android:launchMode="singleInstance">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
@ -175,8 +172,10 @@
<!-- youtube -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="youtube.com" />
@ -254,8 +253,10 @@
<action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="piped.tokhmi.xyz" />

View File

@ -6,7 +6,8 @@
<p style="text-align: center;">Version 3, 29 June 2007</p>
<p>Copyright &copy; 2007 Free Software Foundation, Inc.
&lt;<a href="http://fsf.org/">http://fsf.org/</a>&gt;</p><p>
&lt;<a href="http://fsf.org/">http://fsf.org/</a>&gt;</p>
<p>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.</p>
@ -218,12 +219,14 @@
<ul>
<li>a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.</li>
it, and giving a relevant date.
</li>
<li>b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
&ldquo;keep intact all notices&rdquo;.</li>
&ldquo;keep intact all notices&rdquo;.
</li>
<li>c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
@ -231,12 +234,14 @@
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.</li>
invalidate such permission if you have separately received it.
</li>
<li>d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.</li>
work need not make them do so.
</li>
</ul>
<p>A compilation of a covered work with other separate and independent
@ -260,7 +265,8 @@
<li>a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.</li>
customarily used for software interchange.
</li>
<li>b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
@ -272,13 +278,15 @@
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.</li>
Corresponding Source from a network server at no charge.
</li>
<li>c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.</li>
with subsection 6b.
</li>
<li>d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
@ -291,12 +299,14 @@
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.</li>
available for as long as needed to satisfy these requirements.
</li>
<li>e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.</li>
charge under subsection 6d.
</li>
</ul>
<p>A separable portion of the object code, whose source code is excluded
@ -373,27 +383,33 @@
<ul>
<li>a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or</li>
terms of sections 15 and 16 of this License; or
</li>
<li>b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or</li>
Notices displayed by works containing it; or
</li>
<li>c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or</li>
reasonable ways as different from the original version; or
</li>
<li>d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or</li>
authors of the material; or
</li>
<li>e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or</li>
trade names, trademarks, or service marks; or
</li>
<li>f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.</li>
those licensors and authors.
</li>
</ul>
<p>All other non-permissive additional terms are considered &ldquo;further
@ -629,4 +645,5 @@
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.</p>
</body></html>
</body>
</html>

View File

@ -238,7 +238,8 @@ class DownloadService : Service() {
override fun onDestroy() {
try {
unregisterReceiver(onDownloadComplete)
} catch (e: Exception) { }
} catch (e: Exception) {
}
IS_DOWNLOAD_RUNNING = false
Log.d(TAG, "dl finished!")

View File

@ -33,10 +33,11 @@ import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager
import com.github.libretube.fragments.PlayerFragment
import com.github.libretube.fragments.isFullScreen
import com.github.libretube.preferences.SponsorBlockSettings
import com.github.libretube.util.CronetHelper
import com.github.libretube.util.LocaleHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.updateLanguage
import com.github.libretube.util.updateTheme
import com.github.libretube.util.ThemeHelper
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.color.DynamicColors
@ -68,8 +69,8 @@ class MainActivity : AppCompatActivity() {
SponsorBlockSettings.outroEnabled =
sharedPreferences.getBoolean("outro_category_key", false)
updateTheme(this)
updateLanguage(this)
ThemeHelper().updateTheme(this)
LocaleHelper().updateLanguage(this)
val connectivityManager =
this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

View File

@ -6,7 +6,7 @@ import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.github.libretube.util.restartMainActivity
import com.github.libretube.util.ThemeHelper
class RouterActivity : AppCompatActivity() {
val TAG = "RouterActivity"
@ -17,7 +17,7 @@ class RouterActivity : AppCompatActivity() {
handleSendText(intent)
} else {
// start app as normal if URI not in host list
restartMainActivity(this)
ThemeHelper().restartMainActivity(this)
}
}

View File

@ -1,58 +1,23 @@
package com.github.libretube
import android.Manifest
import android.app.NotificationManager
import android.content.ContentResolver
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
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 androidx.preference.PreferenceManager
import com.github.libretube.dialogs.LoginDialog
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.changeIcon
import com.github.libretube.util.checkUpdate
import com.github.libretube.util.restartMainActivity
import com.github.libretube.util.updateTheme
import com.github.libretube.preferences.SettingsFragment
import com.github.libretube.util.ThemeHelper
import com.google.android.material.color.DynamicColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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
private var isCurrentViewMainSettings = true
private var requireMainActivityRestart = false
var isCurrentViewMainSettings = true
var requireMainActivityRestart = false
class SettingsActivity :
AppCompatActivity(),
SharedPreferences.OnSharedPreferenceChangeListener {
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
DynamicColors.applyToActivityIfAvailable(this)
updateTheme(this)
ThemeHelper().updateTheme(this)
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@ -69,355 +34,18 @@ class SettingsActivity :
.replace(R.id.settings, SettingsFragment())
.commit()
}
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this)
}
override fun onSharedPreferenceChanged(
sharedPreferences: SharedPreferences?,
rootKey: String?
) {
}
class SettingsFragment : PreferenceFragmentCompat() {
val TAG = "Settings"
companion object {
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)
try {
checkUpdate(childFragmentManager)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings, rootKey)
val region = findPreference<Preference>("region")
region?.setOnPreferenceChangeListener { _, _ ->
requireMainActivityRestart = true
true
}
val language = findPreference<ListPreference>("language")
language?.setOnPreferenceChangeListener { _, _ ->
restartMainActivity(requireContext())
true
}
val instance = findPreference<ListPreference>("instance")
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 sponsorblock = findPreference<Preference>("sponsorblock")
sponsorblock?.setOnPreferenceClickListener {
isCurrentViewMainSettings = false
val newFragment = SponsorBlockSettings()
parentFragmentManager.beginTransaction()
.replace(R.id.settings, newFragment)
.commitNow()
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
}
val themeToggle = findPreference<ListPreference>("theme_togglee")
themeToggle?.setOnPreferenceChangeListener { _, _ ->
val refresh = Intent(context, SettingsActivity::class.java)
startActivity(refresh)
requireMainActivityRestart = true
true
}
val accentColor = findPreference<Preference>("accent_color")
accentColor?.setOnPreferenceChangeListener { _, _ ->
requireMainActivityRestart = true
val refresh = Intent(context, SettingsActivity::class.java)
startActivity(refresh)
true
}
val iconChange = findPreference<ListPreference>("icon_change")
iconChange?.setOnPreferenceChangeListener { _, newValue ->
changeIcon(requireContext(), newValue.toString())
true
}
val gridColumns = findPreference<ListPreference>("grid")
gridColumns?.setOnPreferenceChangeListener { _, _ ->
requireMainActivityRestart = true
true
}
val clearHistory = findPreference<Preference>("clear_history")
clearHistory?.setOnPreferenceClickListener {
val sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(requireContext())
sharedPreferences.edit().remove("search_history").commit()
true
}
val about = findPreference<Preference>("about")
about?.setOnPreferenceClickListener {
val uri = Uri.parse("https://libre-tube.github.io/")
val intent = Intent(Intent.ACTION_VIEW).setData(uri)
startActivity(intent)
true
}
val license = findPreference<Preference>("license")
license?.setOnPreferenceClickListener {
val licenseString = view?.context?.assets!!.open("gpl3.html").bufferedReader().use {
it.readText()
}
val licenseHtml = if (Build.VERSION.SDK_INT >= 24) {
Html.fromHtml(licenseString, 1)
} else {
Html.fromHtml(licenseString)
}
MaterialAlertDialogBuilder(view?.context!!)
.setPositiveButton(
getString(R.string.okay),
DialogInterface.OnClickListener { _, _ -> }
)
.setMessage(licenseHtml)
.create()
.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")
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()
}
}
override fun onBackPressed() {
if (isCurrentViewMainSettings) {
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this)
if (requireMainActivityRestart) {
requireMainActivityRestart = false
// kill player notification
val nManager = this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val nManager =
this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
nManager.cancelAll()
restartMainActivity(this)
finishAffinity()
ThemeHelper().restartMainActivity(this)
ActivityCompat.finishAffinity(this)
} else {
super.onBackPressed()
}

View File

@ -43,7 +43,6 @@ import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.IS_DOWNLOAD_RUNNING
import com.github.libretube.MainActivity
import com.github.libretube.R
import com.github.libretube.SponsorBlockSettings
import com.github.libretube.adapters.CommentsAdapter
import com.github.libretube.adapters.TrendingAdapter
import com.github.libretube.dialogs.AddtoPlaylistDialog
@ -56,6 +55,7 @@ import com.github.libretube.obj.Segment
import com.github.libretube.obj.Segments
import com.github.libretube.obj.Streams
import com.github.libretube.obj.Subscribe
import com.github.libretube.preferences.SponsorBlockSettings
import com.github.libretube.util.CronetHelper
import com.github.libretube.util.RetrofitInstance
import com.google.android.exoplayer2.C

View File

@ -0,0 +1,46 @@
package com.github.libretube.preferences
import android.content.Intent
import android.os.Bundle
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.github.libretube.R
import com.github.libretube.SettingsActivity
import com.github.libretube.requireMainActivityRestart
import com.github.libretube.util.ThemeHelper
class AppearanceSettings : PreferenceFragmentCompat() {
private val TAG = "CustomizationSettings"
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.appearance_settings, rootKey)
val themeToggle = findPreference<ListPreference>("theme_togglee")
themeToggle?.setOnPreferenceChangeListener { _, _ ->
val refresh = Intent(context, SettingsActivity::class.java)
startActivity(refresh)
requireMainActivityRestart = true
true
}
val accentColor = findPreference<Preference>("accent_color")
accentColor?.setOnPreferenceChangeListener { _, _ ->
requireMainActivityRestart = true
val refresh = Intent(context, SettingsActivity::class.java)
startActivity(refresh)
true
}
val iconChange = findPreference<ListPreference>("icon_change")
iconChange?.setOnPreferenceChangeListener { _, newValue ->
ThemeHelper().changeIcon(requireContext(), newValue.toString())
true
}
val gridColumns = findPreference<ListPreference>("grid")
gridColumns?.setOnPreferenceChangeListener { _, _ ->
requireMainActivityRestart = true
true
}
}
}

View File

@ -0,0 +1,23 @@
package com.github.libretube.preferences
import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.github.libretube.R
class HistorySettings : PreferenceFragmentCompat() {
private val TAG = "HistorySettings"
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.history_settings, rootKey)
val clearHistory = findPreference<Preference>("clear_history")
clearHistory?.setOnPreferenceClickListener {
val sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(requireContext())
sharedPreferences.edit().remove("search_history").commit()
true
}
}
}

View File

@ -0,0 +1,343 @@
package com.github.libretube.preferences
import android.Manifest
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
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.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.isCurrentViewMainSettings
import com.github.libretube.requireMainActivityRestart
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.ThemeHelper
import com.github.libretube.util.checkUpdate
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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 SettingsFragment : PreferenceFragmentCompat() {
val TAG = "Settings"
companion object {
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)
try {
checkUpdate(childFragmentManager)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings, rootKey)
val region = findPreference<Preference>("region")
region?.setOnPreferenceChangeListener { _, _ ->
requireMainActivityRestart = true
true
}
val language = findPreference<ListPreference>("language")
language?.setOnPreferenceChangeListener { _, _ ->
ThemeHelper().restartMainActivity(requireContext())
true
}
val instance = findPreference<ListPreference>("instance")
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 sponsorblock = findPreference<Preference>("sponsorblock")
sponsorblock?.setOnPreferenceClickListener {
val newFragment = SponsorBlockSettings()
navigateSettings(newFragment)
true
}
val history = findPreference<Preference>("history")
history?.setOnPreferenceClickListener {
val newFragment = HistorySettings()
navigateSettings(newFragment)
true
}
val appearance = findPreference<Preference>("appearance")
appearance?.setOnPreferenceClickListener {
val newFragment = AppearanceSettings()
navigateSettings(newFragment)
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
}
val about = findPreference<Preference>("about")
about?.setOnPreferenceClickListener {
val uri = Uri.parse("https://libre-tube.github.io/")
val intent = Intent(Intent.ACTION_VIEW).setData(uri)
startActivity(intent)
true
}
val license = findPreference<Preference>("license")
license?.setOnPreferenceClickListener {
val licenseString = view?.context?.assets!!
.open("gpl3.html").bufferedReader().use {
it.readText()
}
val licenseHtml = if (Build.VERSION.SDK_INT >= 24) Html.fromHtml(licenseString, 1)
else Html.fromHtml(licenseString)
MaterialAlertDialogBuilder(view?.context!!)
.setPositiveButton(getString(R.string.okay)) { _, _ -> }
.setMessage(licenseHtml)
.create()
.show()
true
}
}
private fun navigateSettings(newFragment: PreferenceFragmentCompat) {
isCurrentViewMainSettings = false
parentFragmentManager.beginTransaction()
.replace(R.id.settings, newFragment)
.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

@ -1,11 +1,12 @@
package com.github.libretube
package com.github.libretube.preferences
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import com.github.libretube.R
class SponsorBlockSettings : PreferenceFragmentCompat() {
private val TAG = "SponsorBlockDialog"
private val TAG = "SponsorBlockSettings"
companion object {
var sponsorBlockEnabled: Boolean = false

View File

@ -0,0 +1,40 @@
package com.github.libretube.util
import android.content.Context
import android.os.Build
import androidx.preference.PreferenceManager
import java.util.*
class LocaleHelper {
fun updateLanguage(context: Context) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val languageName = sharedPreferences.getString("language", "sys")
if (languageName != "") {
setLanguage(context, languageName!!)
}
}
private fun setLanguage(context: Context, languageName: String) {
val locale = if (languageName != "sys" && "$languageName".length < 3) {
Locale(languageName)
} else if ("$languageName".length > 3) {
Locale(languageName?.substring(0, 2), languageName?.substring(4, 6))
} else {
Locale.getDefault()
}
// Change API Language
Locale.setDefault(locale)
// Change App Language
val res = context.resources
val dm = res.displayMetrics
val conf = res.configuration
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
conf.setLocale(locale)
} else {
conf.locale = locale
}
res.updateConfiguration(conf, dm)
}
}

View File

@ -1,97 +0,0 @@
package com.github.libretube.util
import android.app.NotificationManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
import com.github.libretube.R
import java.util.*
fun updateTheme(context: Context) {
updateAccentColor(context)
updateThemeMode(context)
}
fun updateAccentColor(context: Context) {
val colorAccent =
PreferenceManager.getDefaultSharedPreferences(context).getString("accent_color", "red")
when (colorAccent) {
"my" -> context.setTheme(R.style.Theme_MY)
"red" -> context.setTheme(R.style.Theme_Red)
"blue" -> context.setTheme(R.style.Theme_Blue)
"yellow" -> context.setTheme(R.style.Theme_Yellow)
"green" -> context.setTheme(R.style.Theme_Green)
"purple" -> context.setTheme(R.style.Theme_Purple)
}
}
fun updateThemeMode(context: Context) {
val themeMode =
PreferenceManager.getDefaultSharedPreferences(context).getString("theme_togglee", "A")
when (themeMode) {
"A" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
"L" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
"D" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
"O" -> {
context.setTheme(R.style.OLED)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
}
}
fun updateLanguage(context: Context) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val languageName = sharedPreferences.getString("language", "sys")
if (languageName != "") {
val locale = if (languageName != "sys" && "$languageName".length < 3) {
Locale(languageName)
} else if ("$languageName".length > 3) {
Locale(languageName?.substring(0, 2), languageName?.substring(4, 6))
} else {
Locale.getDefault()
}
val res = context.resources
val dm = res.displayMetrics
val conf = res.configuration
// Change App Language
conf.setLocale(locale)
// Change API Language
Locale.setDefault(locale)
res.updateConfiguration(conf, dm)
}
}
fun changeIcon(context: Context, newLogoActivityAlias: String) {
val activityAliases = context.resources.getStringArray(R.array.iconsValue)
// Disable Old Icon(s)
for (activityAlias in activityAliases) {
context.packageManager.setComponentEnabledSetting(
ComponentName(context.packageName, "com.github.libretube.$activityAlias"),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
// Enable New Icon
context.packageManager.setComponentEnabledSetting(
ComponentName(context.packageName, "com.github.libretube.$newLogoActivityAlias"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
}
// Needed due to different MainActivity Aliases because of the app icons
fun restartMainActivity(context: Context) {
// kill player notification
val nManager = context
.getSystemService(AppCompatActivity.NOTIFICATION_SERVICE) as NotificationManager
nManager.cancelAll()
// restart to MainActivity
val pm: PackageManager = context.packageManager
val intent = pm.getLaunchIntentForPackage(context.packageName)
intent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
context.startActivity(intent)
}

View File

@ -0,0 +1,77 @@
package com.github.libretube.util
import android.app.NotificationManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
import com.github.libretube.R
class ThemeHelper {
fun updateTheme(context: Context) {
updateAccentColor(context)
updateThemeMode(context)
}
fun updateAccentColor(context: Context) {
val colorAccent =
PreferenceManager.getDefaultSharedPreferences(context).getString("accent_color", "red")
when (colorAccent) {
"my" -> context.setTheme(R.style.Theme_MY)
"red" -> context.setTheme(R.style.Theme_Red)
"blue" -> context.setTheme(R.style.Theme_Blue)
"yellow" -> context.setTheme(R.style.Theme_Yellow)
"green" -> context.setTheme(R.style.Theme_Green)
"purple" -> context.setTheme(R.style.Theme_Purple)
}
}
fun updateThemeMode(context: Context) {
val themeMode =
PreferenceManager.getDefaultSharedPreferences(context).getString("theme_togglee", "A")
when (themeMode) {
"A" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
"L" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
"D" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
"O" -> {
context.setTheme(R.style.OLED)
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
}
}
fun changeIcon(context: Context, newLogoActivityAlias: String) {
val activityAliases = context.resources.getStringArray(R.array.iconsValue)
// Disable Old Icon(s)
for (activityAlias in activityAliases) {
context.packageManager.setComponentEnabledSetting(
ComponentName(context.packageName, "com.github.libretube.$activityAlias"),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
}
// Enable New Icon
context.packageManager.setComponentEnabledSetting(
ComponentName(context.packageName, "com.github.libretube.$newLogoActivityAlias"),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
}
// Needed due to different MainActivity Aliases because of the app icons
fun restartMainActivity(context: Context) {
// kill player notification
val nManager = context
.getSystemService(AppCompatActivity.NOTIFICATION_SERVICE) as NotificationManager
nManager.cancelAll()
// restart to MainActivity
val pm: PackageManager = context.packageManager
val intent = pm.getLaunchIntentForPackage(context.packageName)
intent?.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
context.startActivity(intent)
}
}

View File

@ -118,4 +118,6 @@
<string name="playOnBackground">Play on background</string>
<string name="update_available">Version %1$s is available</string>
<string name="update_available_text">There is a new update available. Click okay to become redirected to the update page on GitHub.</string>
<string name="appearance">Appearance</string>
<string name="app_behavior">App Behavior</string>
</resources>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="@string/appearance">
<androidx.preference.ListPreference
app:title="@string/app_theme"
app:key="theme_togglee"
app:entries="@array/themes"
app:entryValues="@array/themesValue"
app:defaultValue="A"
android:icon="@drawable/ic_theme" />
<androidx.preference.ListPreference
app:title="@string/color_accent"
app:key="accent_color"
app:entries="@array/accents"
app:entryValues="@array/accentsValue"
app:defaultValue="red"
android:icon="@drawable/ic_color" />
<androidx.preference.ListPreference
app:title="@string/app_icon"
app:key="icon_change"
app:entries="@array/icons"
app:entryValues="@array/iconsValue"
app:defaultValue="MainActivity"
android:icon="@drawable/ic_frame" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/app_behavior">
<androidx.preference.ListPreference
app:title="@string/defaultTab"
app:key="default_tab"
app:entries="@array/tabs"
app:entryValues="@array/tabsValue"
app:defaultValue="home"
android:icon="@drawable/ic_home_outlined" />
<androidx.preference.ListPreference
app:title="@string/defres"
app:key="default_res"
app:entries="@array/defres"
app:entryValues="@array/defresValue"
app:defaultValue=""
android:icon="@drawable/ic_hd"
app:useSimpleSummaryProvider="true" />
<androidx.preference.ListPreference
app:title="@string/grid"
app:key="grid"
app:entries="@array/grid"
app:entryValues="@array/grid"
app:defaultValue="@integer/grid_items"
android:icon="@drawable/ic_grid"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory app:title="@string/search_history">
<SwitchPreference
app:title="@string/search_history"
app:key="search_history_toggle"
android:defaultValue="true"
android:icon="@drawable/ic_history" />
<Preference
app:title="@string/clear_history"
app:key="clear_history"
android:icon="@drawable/ic_delete" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -54,80 +54,24 @@
</PreferenceCategory>
<PreferenceCategory app:title="@string/customization">
<ListPreference
app:title="@string/app_theme"
app:key="theme_togglee"
app:entries="@array/themes"
app:entryValues="@array/themesValue"
app:defaultValue="A"
android:icon="@drawable/ic_theme" />
<ListPreference
app:title="@string/color_accent"
app:key="accent_color"
app:entries="@array/accents"
app:entryValues="@array/accentsValue"
app:defaultValue="red"
<Preference
app:key="appearance"
app:title="@string/appearance"
android:icon="@drawable/ic_color" />
<ListPreference
app:title="@string/app_icon"
app:key="icon_change"
app:entries="@array/icons"
app:entryValues="@array/iconsValue"
app:defaultValue="MainActivity"
android:icon="@drawable/ic_frame" />
<androidx.preference.Preference
app:title="@string/sponsorblock"
app:key="sponsorblock"
app:summary="@string/sponsorblock_summary"
android:icon="@drawable/ic_block" />
<ListPreference
app:title="@string/defaultTab"
app:key="default_tab"
app:entries="@array/tabs"
app:entryValues="@array/tabsValue"
app:defaultValue="home"
android:icon="@drawable/ic_home_outlined" />
<ListPreference
app:title="@string/defres"
app:key="default_res"
app:entries="@array/defres"
app:entryValues="@array/defresValue"
app:defaultValue=""
android:icon="@drawable/ic_hd"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:title="@string/grid"
app:key="grid"
app:entries="@array/grid"
app:entryValues="@array/grid"
app:defaultValue="@integer/grid_items"
android:icon="@drawable/ic_grid"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/history">
<SwitchPreference
app:title="@string/search_history"
app:key="search_history_toggle"
android:defaultValue="true"
<androidx.preference.Preference
app:key="history"
app:title="@string/history"
android:icon="@drawable/ic_history" />
<Preference
app:title="@string/clear_history"
app:key="clear_history"
android:icon="@drawable/ic_delete" />
</PreferenceCategory>
<PreferenceCategory app:title="@string/about">