let extensions patch all resources

This commit is contained in:
Cloudburst 2022-09-16 17:01:16 +02:00
parent 9402a28041
commit 6baeadf55e
12 changed files with 277 additions and 5 deletions

View file

@ -18,6 +18,7 @@ import com.lagradost.cloudstream3.utils.DataStore.getKeys
import com.lagradost.cloudstream3.utils.DataStore.removeKey
import com.lagradost.cloudstream3.utils.DataStore.removeKeys
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.resources.ResourcePackManager
import kotlinx.coroutines.runBlocking
import org.acra.ACRA
import org.acra.ReportField
@ -79,6 +80,7 @@ class ExceptionHandler(val errorFile: File, val onError: (() -> Unit)): Thread.U
ACRA.errorReporter.handleException(error)
try {
PrintStream(errorFile).use { ps ->
ps.println(String.format("Enabled resource pack: ${ResourcePackManager.activePackId ?: "none"}"))
ps.println(String.format("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}"))
ps.println(String.format("Fatal exception on thread %s (%d)", thread.name, thread.id))
error.printStackTrace(ps)

View file

@ -4,13 +4,13 @@ import android.content.ComponentName
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager
import android.widget.Toast
import androidx.annotation.IdRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
@ -59,6 +59,9 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSet
import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.IOnBackPressed
import com.lagradost.cloudstream3.utils.resources.ResourcePatch
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
@ -70,8 +73,6 @@ import com.lagradost.cloudstream3.utils.DataStore.removeKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.IOnBackPressed
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
@ -82,6 +83,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.utils.resources.ResourcePackManager
import com.lagradost.cloudstream3.utils.resources.ResourcePatchActivity
import com.lagradost.nicehttp.Requests
import com.lagradost.nicehttp.ResponseParser
import kotlinx.android.synthetic.main.activity_main.*
@ -133,7 +136,18 @@ var app = Requests(responseParser = object : ResponseParser {
defaultHeaders = mapOf("user-agent" to USER_AGENT)
}
class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
class MainActivity : AppCompatActivity(), ColorPickerDialogListener, ResourcePatchActivity {
private var resourcePatch: ResourcePatch? = null
override fun getResources(): Resources = resourcePatch ?: super.getResources()
override fun reloadResourcePatch() {
resourcePatch = try {
ResourcePackManager.activePack?.invoke(super.getResources())
} catch (e: Throwable) {
null
}
}
companion object {
const val TAG = "MAINACT"

View file

@ -9,6 +9,8 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.extractorApis
import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.utils.resources.ResourcePackManager
import com.lagradost.cloudstream3.utils.resources.ResourcePatch
const val PLUGIN_TAG = "PluginInstance"
@ -50,6 +52,16 @@ abstract class Plugin {
extractorApis.add(element)
}
/**
* Used to register a new resource pack
* @param factory The function that provided the original resources will generate a ResourcePatch instance
* You should probably use MapResourcePatch
* @see com.lagradost.cloudstream3.utils.resources.MapResourcePatch
*/
fun registerResourcePack(name: String, factory: (Resources) -> ResourcePatch) {
ResourcePackManager.registerPack(name, factory, this.__filename)
}
class Manifest {
@JsonProperty("name") var name: String? = null
@JsonProperty("pluginClassName") var pluginClassName: String? = null

View file

@ -27,6 +27,7 @@ import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIE
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.extractorApis
import com.lagradost.cloudstream3.utils.resources.ResourcePackManager
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.acra.log.debug
@ -376,6 +377,7 @@ object PluginManager {
}
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename }
extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.__filename }
ResourcePackManager.unregisterPlugin(plugin.__filename)
classLoaders.values.removeIf { v -> v == plugin }

View file

@ -1,6 +1,7 @@
package com.lagradost.cloudstream3.ui.player
import android.content.Intent
import android.content.res.Resources
import android.net.Uri
import android.os.Bundle
import android.util.Log
@ -11,10 +12,13 @@ import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.ExtractorUri
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.resources.ResourcePackManager
import com.lagradost.cloudstream3.utils.resources.ResourcePatch
import com.lagradost.cloudstream3.utils.resources.ResourcePatchActivity
const val DTAG = "PlayerActivity"
class DownloadedPlayerActivity : AppCompatActivity() {
class DownloadedPlayerActivity : AppCompatActivity(), ResourcePatchActivity {
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
CommonActivity.dispatchKeyEvent(this, event)?.let {
return it
@ -106,4 +110,14 @@ class DownloadedPlayerActivity : AppCompatActivity() {
return
}
}
private var resourcePatch: ResourcePatch? = null
override fun getResources(): Resources = resourcePatch ?: super.getResources()
override fun reloadResourcePatch() {
resourcePatch = try {
ResourcePackManager.activePack?.invoke(super.getResources())
} catch (e: Throwable) {
null
}
}
}

View file

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.View
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchQuality
import com.lagradost.cloudstream3.mvvm.logError
@ -16,6 +17,8 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.resources.ResourcePackManager
import kotlin.streams.toList
class SettingsUI : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -130,6 +133,27 @@ class SettingsUI : PreferenceFragmentCompat() {
}
return@setOnPreferenceClickListener true
}
getPref(R.string.active_resource_pack_key)?.setOnPreferenceClickListener {
val prefNames = ResourcePackManager.packs.keys.toMutableList()
prefNames.add(0, getString(R.string.none))
val prefValues: MutableList<String?> = ResourcePackManager.packs.keys.toMutableList()
prefValues.add(0, null)
activity?.showDialog(
prefNames.toList(),
prefValues.indexOf(ResourcePackManager.activePackId),
getString(R.string.resource_pack),
true,
{}) {
try {
ResourcePackManager.selectPack(prefValues[it], activity as MainActivity)
activity?.recreate()
} catch (e: Exception) {
logError(e)
}
}
return@setOnPreferenceClickListener true
}
getPref(R.string.pref_filter_search_quality_key)?.setOnPreferenceClickListener {
val names = enumValues<SearchQuality>().sorted().map { it.name }

View file

@ -0,0 +1,47 @@
package com.lagradost.cloudstream3.utils.resources
import android.content.res.ColorStateList
import android.content.res.Resources
import android.content.res.XmlResourceParser
import android.graphics.drawable.Drawable
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
typealias Generator<T> = () -> T?
typealias GeneratorWithTheme<T> = (Resources.Theme?) -> T?
open class MapResourcePatch(private val original: Resources): ResourcePatch(original) {
val mapping: HashMap<Int, Any> = hashMapOf()
val idMapping: HashMap<Int, Int> = hashMapOf()
override fun mapId(id: Int): Int =
idMapping[id] ?: super.mapId(id)
override fun getLayout(id: Int): XmlResourceParser =
mapping.getMappingKA(id, null) ?: super.getLayout(id)
override fun getDrawable(id: Int, theme: Theme?): Drawable =
mapping.getMappingKA(id, theme) ?: super.getDrawable(id, theme)
@RequiresApi(Build.VERSION_CODES.M)
override fun getColorStateList(id: Int, theme: Theme?): ColorStateList =
mapping.getMappingKA(id, theme) ?: super.getColorStateList(id, theme)
override fun getXml(id: Int): XmlResourceParser =
mapping.getMappingKA(id, null) ?: super.getXml(id)
@RequiresApi(Build.VERSION_CODES.M)
override fun getColor(id: Int, theme: Theme?): Int =
mapping.getMappingKA(id, theme) ?: super.getColor(id, theme)
}
private inline fun <K, reified V> HashMap<K, Any>.getMappingKA(id: K?, theme: Resources.Theme?): V? {
return when (val res = this[id]) {
equals(null) -> null
is V -> res
is Function0<*> -> (res as? Generator<V>)?.invoke()
is Function1<*, *> -> (res as? GeneratorWithTheme<V>)?.invoke(theme)
else -> null
}
}

View file

@ -0,0 +1,66 @@
package com.lagradost.cloudstream3.utils.resources
import android.content.res.Resources
import android.util.Log
import com.lagradost.cloudstream3.AcraApplication
import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.R
typealias PatchFactory = (Resources) -> ResourcePatch
object ResourcePackManager {
private const val KEY = "ResourcePackManager"
const val SETTINGS_KEY = "active_resource_pack_key" // TODO
val packs: HashMap<String, PatchFactory> = hashMapOf()
private val pluginMapping: HashMap<String, ArrayList<String>> = hashMapOf()
var activePackId: String? = null
internal set
val activePack: PatchFactory?
get() = if (activePackId == null) null else packs[activePackId]
init {
afterPluginsLoadedEvent += ::onExtensionsLoaded
}
fun registerPack(name: String, factory: PatchFactory, pluginId: String?) {
packs[name] = factory
if (pluginId != null) {
if (!pluginMapping.containsKey(pluginId))
pluginMapping[pluginId] = arrayListOf()
pluginMapping[pluginId]?.add(name)
}
}
fun unregisterPlugin(pluginId: String?) {
pluginMapping[pluginId]?.forEach {
packs.remove(it)
if (activePackId == it) // if pack is being removed make sure its not set
selectPack(null, AcraApplication.context?.getActivity() as? ResourcePatchActivity)
}
pluginMapping.remove(pluginId)
}
fun selectPack(packId: String?, activity: ResourcePatchActivity?) {
if (packId == null) activePackId = null
else if (packs.containsKey(packId)) activePackId = packId
else activePackId = null
Log.d(KEY, "Selecting: ${activePackId ?: "none"}")
setKey(SETTINGS_KEY, activePackId)
activity?.reloadResourcePatch()
}
private fun onExtensionsLoaded(successful: Boolean = false) {
if (!successful) return
activePackId = getKey(SETTINGS_KEY)
Log.d(KEY, "Selecting saved: ${activePackId ?: "none"}")
val activity = AcraApplication.context?.getActivity() as? ResourcePatchActivity
activity?.reloadResourcePatch()
}
}

View file

@ -0,0 +1,80 @@
package com.lagradost.cloudstream3.utils.resources
import android.annotation.SuppressLint
import android.content.res.*
import android.content.res.loader.ResourcesLoader
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.util.TypedValue
import androidx.annotation.RequiresApi
import java.io.InputStream
open class ResourcePatch(private val original: Resources) :
Resources(original.assets, original.displayMetrics, original.configuration) {
open fun mapId(id: Int): Int = id
@RequiresApi(Build.VERSION_CODES.R)
override fun addLoaders(vararg loaders: ResourcesLoader?) = original.addLoaders(*loaders)
override fun getAnimation(id: Int): XmlResourceParser = original.getAnimation(mapId(id))
override fun getBoolean(id: Int): Boolean = original.getBoolean(mapId(id))
@RequiresApi(Build.VERSION_CODES.M)
override fun getColor(id: Int, theme: Theme?): Int = original.getColor(mapId(id), theme)
override fun getConfiguration(): Configuration = original.configuration
override fun getDisplayMetrics(): DisplayMetrics = original.displayMetrics
@RequiresApi(Build.VERSION_CODES.M)
override fun getColorStateList(id: Int, theme: Theme?): ColorStateList = original.getColorStateList(mapId(id), theme)
override fun getLayout(id: Int): XmlResourceParser = original.getLayout(mapId(id))
override fun getDimension(id: Int): Float = original.getDimension(mapId(id))
override fun getIdentifier(name: String?, defType: String?, defPackage: String?): Int = original.getIdentifier(name, defType, defPackage)
override fun getDimensionPixelOffset(id: Int): Int = original.getDimensionPixelOffset(mapId(id))
override fun getDimensionPixelSize(id: Int): Int = original.getDimensionPixelSize(mapId(id))
@SuppressLint("UseCompatLoadingForDrawables")
override fun getDrawable(id: Int, theme: Theme?): Drawable = original.getDrawable(mapId(id), theme)
override fun getDrawableForDensity(id: Int, density: Int, theme: Theme?): Drawable? = original.getDrawableForDensity(mapId(id), density, theme)
@RequiresApi(Build.VERSION_CODES.Q)
override fun getFloat(id: Int): Float = original.getFloat(mapId(id))
@RequiresApi(Build.VERSION_CODES.O)
override fun getFont(id: Int): Typeface = original.getFont(mapId(id))
override fun getIntArray(id: Int): IntArray = original.getIntArray(mapId(id))
override fun getFraction(id: Int, base: Int, pbase: Int): Float = original.getFraction(mapId(id), base, pbase)
override fun getInteger(id: Int): Int = original.getInteger(mapId(id))
override fun getQuantityString(id: Int, quantity: Int): String = original.getQuantityString(mapId(id), quantity)
override fun getQuantityString(id: Int, quantity: Int, vararg formatArgs: Any?): String = original.getQuantityString(mapId(id), quantity, *formatArgs)
override fun getQuantityText(id: Int, quantity: Int): CharSequence = original.getQuantityText(mapId(id), quantity)
override fun getResourceEntryName(id: Int): String = original.getResourceEntryName(mapId(id))
override fun getResourceName(id: Int): String = original.getResourceName(mapId(id))
override fun getResourcePackageName(id: Int): String = original.getResourcePackageName(mapId(id))
override fun getResourceTypeName(id: Int): String = original.getResourceTypeName(mapId(id))
override fun getString(id: Int): String = original.getString(mapId(id))
override fun getString(id: Int, vararg formatArgs: Any?): String = original.getString(mapId(id), *formatArgs)
override fun getStringArray(id: Int): Array<String> = original.getStringArray(mapId(id))
override fun getText(id: Int): CharSequence = original.getText(mapId(id))
override fun getText(id: Int, def: CharSequence?): CharSequence = original.getText(mapId(id), def)
override fun getTextArray(id: Int): Array<CharSequence> = original.getTextArray(mapId(id))
override fun getValue(id: Int, outValue: TypedValue?, resolveRefs: Boolean) = original.getValue(mapId(id), outValue, resolveRefs)
override fun getValue(name: String?, outValue: TypedValue?, resolveRefs: Boolean) = original.getValue(name, outValue, resolveRefs)
override fun getValueForDensity(id: Int, density: Int, outValue: TypedValue?, resolveRefs: Boolean) = original.getValueForDensity(mapId(id), density, outValue, resolveRefs)
override fun getXml(id: Int): XmlResourceParser = original.getXml(mapId(id))
override fun obtainTypedArray(id: Int): TypedArray = original.obtainTypedArray(mapId(id))
override fun openRawResource(id: Int): InputStream = original.openRawResource(mapId(id))
override fun openRawResource(id: Int, value: TypedValue?): InputStream = original.openRawResource(mapId(id), value)
override fun openRawResourceFd(id: Int): AssetFileDescriptor = original.openRawResourceFd(mapId(id))
override fun obtainAttributes(set: AttributeSet?, attrs: IntArray?): TypedArray = original.obtainAttributes(set, attrs)
override fun parseBundleExtra(tagName: String?, attrs: AttributeSet?, outBundle: Bundle?) = original.parseBundleExtra(tagName, attrs, outBundle)
override fun parseBundleExtras(parser: XmlResourceParser?, outBundle: Bundle?) = original.parseBundleExtras(parser, outBundle)
@RequiresApi(Build.VERSION_CODES.R)
override fun removeLoaders(vararg loaders: ResourcesLoader?) = original.removeLoaders(*loaders)
override fun equals(other: Any?): Boolean = original == other
override fun hashCode(): Int = original.hashCode()
override fun getColor(id: Int): Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) getColor(mapId(id), null) else original.getColor(mapId(id))
@SuppressLint("UseCompatLoadingForColorStateLists")
override fun getColorStateList(id: Int): ColorStateList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) getColorStateList(mapId(id), null) else original.getColorStateList(mapId(id))
@SuppressLint("UseCompatLoadingForDrawables")
override fun getDrawable(id: Int): Drawable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) getDrawable(mapId(id), null) else original.getDrawable(mapId(id))
}

View file

@ -0,0 +1,5 @@
package com.lagradost.cloudstream3.utils.resources
interface ResourcePatchActivity {
fun reloadResourcePatch()
}

View file

@ -41,6 +41,7 @@
<string name="app_name_download_path" translatable="false">Cloudstream</string>
<string name="app_layout_key" translatable="false">app_layout_key</string>
<string name="primary_color_key" translatable="false">primary_color_key</string>
<string name="active_resource_pack_key" translatable="false">active_resource_pack_key</string>
<string name="restore_key" translatable="false">restore_key</string>
<string name="backup_key" translatable="false">backup_key</string>
<string name="prefer_media_type_key" translatable="false">prefer_media_type_key_2</string>
@ -457,6 +458,7 @@
<string name="primary_color_settings">Primary color</string>
<string name="app_theme_settings">App theme</string>
<string name="resource_pack">Resource pack</string>
<string name="bottom_title_settings">Poster title location</string>
<string name="bottom_title_settings_des">Put the title under the poster</string>

View file

@ -9,6 +9,10 @@
android:icon="@drawable/ic_baseline_color_lens_24"
android:key="@string/app_theme_key"
android:title="@string/app_theme_settings" />
<Preference
android:icon="@drawable/ic_baseline_extension_24"
android:key="@string/active_resource_pack_key"
android:title="@string/resource_pack" />
<Preference
android:icon="@drawable/ic_baseline_tv_24"
android:key="@string/app_layout_key"