mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
483 lines
No EOL
16 KiB
Kotlin
483 lines
No EOL
16 KiB
Kotlin
package com.lagradost.cloudstream3.utils
|
|
|
|
import android.Manifest
|
|
import android.annotation.SuppressLint
|
|
import android.app.Activity
|
|
import android.app.AppOpsManager
|
|
import android.app.Dialog
|
|
import android.content.Context
|
|
import android.content.pm.PackageManager
|
|
import android.content.res.Configuration
|
|
import android.content.res.Resources
|
|
import android.graphics.Color
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import android.view.*
|
|
import android.view.inputmethod.InputMethodManager
|
|
import android.widget.ImageView
|
|
import android.widget.ListAdapter
|
|
import android.widget.ListView
|
|
import androidx.annotation.AttrRes
|
|
import androidx.annotation.ColorInt
|
|
import androidx.annotation.DrawableRes
|
|
import androidx.annotation.IdRes
|
|
import androidx.appcompat.view.ContextThemeWrapper
|
|
import androidx.appcompat.view.menu.MenuBuilder
|
|
import androidx.appcompat.widget.PopupMenu
|
|
import androidx.core.app.ActivityCompat
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.core.graphics.alpha
|
|
import androidx.core.graphics.blue
|
|
import androidx.core.graphics.green
|
|
import androidx.core.graphics.red
|
|
import androidx.fragment.app.Fragment
|
|
import androidx.fragment.app.FragmentActivity
|
|
import androidx.navigation.fragment.NavHostFragment
|
|
import androidx.preference.PreferenceManager
|
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
import com.bumptech.glide.load.model.GlideUrl
|
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
|
import com.lagradost.cloudstream3.R
|
|
import com.lagradost.cloudstream3.mvvm.logError
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
|
import com.lagradost.cloudstream3.utils.GlideOptions.bitmapTransform
|
|
import jp.wasabeef.glide.transformations.BlurTransformation
|
|
import kotlin.math.roundToInt
|
|
|
|
|
|
object UIHelper {
|
|
val Int.toPx: Int get() = (this * Resources.getSystem().displayMetrics.density).toInt()
|
|
val Float.toPx: Float get() = (this * Resources.getSystem().displayMetrics.density)
|
|
val Int.toDp: Int get() = (this / Resources.getSystem().displayMetrics.density).toInt()
|
|
val Float.toDp: Float get() = (this / Resources.getSystem().displayMetrics.density)
|
|
|
|
fun Activity.checkWrite(): Boolean {
|
|
return (ContextCompat.checkSelfPermission(
|
|
this,
|
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
)
|
|
== PackageManager.PERMISSION_GRANTED
|
|
// Since Android 13, we can't request external storage permission,
|
|
// so don't check it.
|
|
|| Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
|
}
|
|
|
|
fun Activity.requestRW() {
|
|
ActivityCompat.requestPermissions(
|
|
this,
|
|
arrayOf(
|
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
|
Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
Manifest.permission.MANAGE_EXTERNAL_STORAGE
|
|
),
|
|
1337
|
|
)
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets ListView height dynamically based on the height of the items.
|
|
*
|
|
* @param listView to be resized
|
|
* @return true if the listView is successfully resized, false otherwise
|
|
*/
|
|
fun setListViewHeightBasedOnItems(listView: ListView?) {
|
|
val listAdapter: ListAdapter = listView?.adapter ?: return
|
|
val numberOfItems: Int = listAdapter.count
|
|
|
|
// Get total height of all items.
|
|
var totalItemsHeight = 0
|
|
for (itemPos in 0 until numberOfItems) {
|
|
val item: View = listAdapter.getView(itemPos, null, listView)
|
|
item.measure(0, 0)
|
|
totalItemsHeight += item.measuredHeight
|
|
}
|
|
|
|
// Get total height of all item dividers.
|
|
val totalDividersHeight: Int = listView.dividerHeight *
|
|
(numberOfItems - 1)
|
|
|
|
// Set list height.
|
|
val params: ViewGroup.LayoutParams = listView.layoutParams
|
|
params.height = totalItemsHeight + totalDividersHeight
|
|
listView.layoutParams = params
|
|
listView.requestLayout()
|
|
}
|
|
|
|
fun Activity?.getSpanCount(): Int? {
|
|
val compactView = false
|
|
val spanCountLandscape = if (compactView) 2 else 6
|
|
val spanCountPortrait = if (compactView) 1 else 3
|
|
val orientation = this?.resources?.configuration?.orientation ?: return null
|
|
|
|
return if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
spanCountLandscape
|
|
} else {
|
|
spanCountPortrait
|
|
}
|
|
}
|
|
|
|
fun Fragment.hideKeyboard() {
|
|
activity?.window?.decorView?.clearFocus()
|
|
view?.let {
|
|
hideKeyboard(it)
|
|
}
|
|
}
|
|
|
|
fun Activity.hideKeyboard() {
|
|
window?.decorView?.clearFocus()
|
|
this.findViewById<View>(android.R.id.content)?.rootView?.let {
|
|
hideKeyboard(it)
|
|
}
|
|
}
|
|
|
|
fun Activity?.navigate(@IdRes navigation: Int, arguments: Bundle? = null) {
|
|
try {
|
|
if (this is FragmentActivity) {
|
|
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment?)?.navController?.navigate(
|
|
navigation, arguments
|
|
)
|
|
}
|
|
} catch (t: Throwable) {
|
|
logError(t)
|
|
}
|
|
}
|
|
|
|
@ColorInt
|
|
fun Context.getResourceColor(@AttrRes resource: Int, alphaFactor: Float = 1f): Int {
|
|
val typedArray = obtainStyledAttributes(intArrayOf(resource))
|
|
val color = typedArray.getColor(0, 0)
|
|
typedArray.recycle()
|
|
|
|
if (alphaFactor < 1f) {
|
|
val alpha = (color.alpha * alphaFactor).roundToInt()
|
|
return Color.argb(alpha, color.red, color.green, color.blue)
|
|
}
|
|
|
|
return color
|
|
}
|
|
|
|
fun ImageView?.setImage(
|
|
url: String?,
|
|
headers: Map<String, String>? = null,
|
|
@DrawableRes
|
|
errorImageDrawable: Int? = null,
|
|
fadeIn: Boolean = true
|
|
): Boolean {
|
|
if (this == null || url.isNullOrBlank()) return false
|
|
|
|
return try {
|
|
val builder = GlideApp.with(this)
|
|
.load(GlideUrl(url) { headers ?: emptyMap() })
|
|
.skipMemoryCache(true)
|
|
.diskCacheStrategy(DiskCacheStrategy.ALL).let { req ->
|
|
if (fadeIn)
|
|
req.transition(DrawableTransitionOptions.withCrossFade())
|
|
else req
|
|
}
|
|
|
|
val res = if (errorImageDrawable != null)
|
|
builder.error(errorImageDrawable).into(this)
|
|
else
|
|
builder.into(this)
|
|
res.clearOnDetach()
|
|
|
|
true
|
|
} catch (e: Exception) {
|
|
logError(e)
|
|
false
|
|
}
|
|
}
|
|
|
|
fun ImageView?.setImageBlur(
|
|
url: String?,
|
|
radius: Int,
|
|
sample: Int = 3,
|
|
headers: Map<String, String>? = null
|
|
) {
|
|
if (this == null || url.isNullOrBlank()) return
|
|
try {
|
|
val res = GlideApp.with(this)
|
|
.load(GlideUrl(url) { headers ?: emptyMap() })
|
|
.apply(bitmapTransform(BlurTransformation(radius, sample)))
|
|
.transition(
|
|
DrawableTransitionOptions.withCrossFade()
|
|
)
|
|
.skipMemoryCache(true)
|
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
|
.into(this)
|
|
res.clearOnDetach()
|
|
} catch (e: Exception) {
|
|
logError(e)
|
|
}
|
|
}
|
|
|
|
fun adjustAlpha(@ColorInt color: Int, factor: Float): Int {
|
|
val alpha = (Color.alpha(color) * factor).roundToInt()
|
|
val red = Color.red(color)
|
|
val green = Color.green(color)
|
|
val blue = Color.blue(color)
|
|
return Color.argb(alpha, red, green, blue)
|
|
}
|
|
|
|
fun Context.colorFromAttribute(attribute: Int): Int {
|
|
val attributes = obtainStyledAttributes(intArrayOf(attribute))
|
|
val color = attributes.getColor(0, 0)
|
|
attributes.recycle()
|
|
return color
|
|
}
|
|
|
|
fun Activity.hideSystemUI() {
|
|
// Enables regular immersive mode.
|
|
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
|
|
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
window.decorView.systemUiVisibility = (
|
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
// Set the content to appear under the system bars so that the
|
|
// content doesn't resize when the system bars hide and show.
|
|
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
// Hide the nav bar and status bar
|
|
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
// or View.SYSTEM_UI_FLAG_LOW_PROFILE
|
|
)
|
|
// window.addFlags(View.KEEP_SCREEN_ON)
|
|
}
|
|
|
|
fun FragmentActivity.popCurrentPage() {
|
|
this.onBackPressed()
|
|
/*val currentFragment = supportFragmentManager.fragments.lastOrNull {
|
|
it.isVisible
|
|
} ?: return
|
|
|
|
supportFragmentManager.beginTransaction()
|
|
.setCustomAnimations(
|
|
R.anim.enter_anim,
|
|
R.anim.exit_anim,
|
|
R.anim.pop_enter,
|
|
R.anim.pop_exit
|
|
)
|
|
.remove(currentFragment)
|
|
.commitAllowingStateLoss()*/
|
|
}
|
|
/*
|
|
fun FragmentActivity.popCurrentPage(isInPlayer: Boolean, isInExpandedView: Boolean, isInResults: Boolean) {
|
|
val currentFragment = supportFragmentManager.fragments.lastOrNull {
|
|
it.isVisible
|
|
}
|
|
?: //this.onBackPressed()
|
|
return
|
|
|
|
/*
|
|
if (tvActivity == null) {
|
|
requestedOrientation = if (settingsManager?.getBoolean("force_landscape", false) == true) {
|
|
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
|
} else {
|
|
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
|
}
|
|
}*/
|
|
|
|
// No fucked animations leaving the player :)
|
|
when {
|
|
isInPlayer -> {
|
|
supportFragmentManager.beginTransaction()
|
|
//.setCustomAnimations(R.anim.enter, R.anim.exit, R.anim.pop_enter, R.anim.pop_exit)
|
|
.remove(currentFragment)
|
|
.commitAllowingStateLoss()
|
|
}
|
|
isInExpandedView && !isInResults -> {
|
|
supportFragmentManager.beginTransaction()
|
|
.setCustomAnimations(
|
|
R.anim.enter_anim,//R.anim.enter_from_right,
|
|
R.anim.exit_anim,//R.anim.exit_to_right,
|
|
R.anim.pop_enter,
|
|
R.anim.pop_exit
|
|
)
|
|
.remove(currentFragment)
|
|
.commitAllowingStateLoss()
|
|
}
|
|
else -> {
|
|
supportFragmentManager.beginTransaction()
|
|
.setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit)
|
|
.remove(currentFragment)
|
|
.commitAllowingStateLoss()
|
|
}
|
|
}
|
|
}*/
|
|
|
|
fun Context.getStatusBarHeight(): Int {
|
|
if (isTvSettings()) {
|
|
return 0
|
|
}
|
|
|
|
var result = 0
|
|
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
|
if (resourceId > 0) {
|
|
result = resources.getDimensionPixelSize(resourceId)
|
|
}
|
|
return result
|
|
}
|
|
|
|
fun Context?.fixPaddingStatusbar(v: View?) {
|
|
if (v == null || this == null) return
|
|
v.setPadding(
|
|
v.paddingLeft,
|
|
v.paddingTop + getStatusBarHeight(),
|
|
v.paddingRight,
|
|
v.paddingBottom
|
|
)
|
|
}
|
|
|
|
fun Context.fixPaddingStatusbarView(v: View?) {
|
|
if (v == null) return
|
|
|
|
val params = v.layoutParams
|
|
params.height = getStatusBarHeight()
|
|
v.layoutParams = params
|
|
}
|
|
|
|
fun Context.getNavigationBarHeight(): Int {
|
|
var result = 0
|
|
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
|
|
if (resourceId > 0) {
|
|
result = resources.getDimensionPixelSize(resourceId)
|
|
}
|
|
return result
|
|
}
|
|
|
|
fun Context?.IsBottomLayout(): Boolean {
|
|
if (this == null) return true
|
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
|
return settingsManager.getBoolean(getString(R.string.bottom_title_key), true)
|
|
}
|
|
|
|
fun Activity.changeStatusBarState(hide: Boolean): Int {
|
|
return if (hide) {
|
|
window?.setFlags(
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
|
)
|
|
0
|
|
} else {
|
|
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
|
this.getStatusBarHeight()
|
|
}
|
|
}
|
|
|
|
// Shows the system bars by removing all the flags
|
|
// except for the ones that make the content appear under the system bars.
|
|
fun Activity.showSystemUI() {
|
|
window.decorView.systemUiVisibility =
|
|
|
|
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
|
|
|
changeStatusBarState(isEmulatorSettings())
|
|
|
|
// window.clearFlags(View.KEEP_SCREEN_ON)
|
|
}
|
|
|
|
fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {
|
|
return try {
|
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
|
settingsManager?.getBoolean(
|
|
getString(R.string.pip_enabled_key),
|
|
true
|
|
) ?: true && isInPlayer
|
|
} catch (e: Exception) {
|
|
logError(e)
|
|
false
|
|
}
|
|
}
|
|
|
|
fun Context.hasPIPPermission(): Boolean {
|
|
val appOps =
|
|
getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
|
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
appOps.checkOpNoThrow(
|
|
AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
|
|
android.os.Process.myUid(),
|
|
packageName
|
|
) == AppOpsManager.MODE_ALLOWED
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
fun hideKeyboard(view: View?) {
|
|
if (view == null) return
|
|
|
|
val inputMethodManager =
|
|
view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?
|
|
inputMethodManager?.hideSoftInputFromWindow(view.windowToken, 0)
|
|
}
|
|
|
|
fun showInputMethod(view: View?) {
|
|
if (view == null) return
|
|
val inputMethodManager =
|
|
view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?
|
|
inputMethodManager?.showSoftInput(view, 0)
|
|
}
|
|
|
|
fun Dialog?.dismissSafe(activity: Activity?) {
|
|
if (this?.isShowing == true && activity?.isFinishing == false) {
|
|
this.dismiss()
|
|
}
|
|
}
|
|
|
|
fun Dialog?.dismissSafe() {
|
|
if (this?.isShowing == true) {
|
|
this.dismiss()
|
|
}
|
|
}
|
|
|
|
/**id, stringRes */
|
|
@SuppressLint("RestrictedApi")
|
|
fun View.popupMenuNoIcons(
|
|
items: List<Pair<Int, Int>>,
|
|
onMenuItemClick: MenuItem.() -> Unit,
|
|
): PopupMenu {
|
|
val ctw = ContextThemeWrapper(context, R.style.PopupMenu)
|
|
val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
|
|
|
|
items.forEach { (id, stringRes) ->
|
|
popup.menu.add(0, id, 0, stringRes)
|
|
}
|
|
|
|
(popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true)
|
|
|
|
popup.setOnMenuItemClickListener {
|
|
it.onMenuItemClick()
|
|
true
|
|
}
|
|
|
|
popup.show()
|
|
return popup
|
|
}
|
|
|
|
/**id, string */
|
|
@SuppressLint("RestrictedApi")
|
|
fun View.popupMenuNoIconsAndNoStringRes(
|
|
items: List<Pair<Int, String>>,
|
|
onMenuItemClick: MenuItem.() -> Unit,
|
|
): PopupMenu {
|
|
val ctw = ContextThemeWrapper(context, R.style.PopupMenu)
|
|
val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
|
|
|
|
items.forEach { (id, string) ->
|
|
popup.menu.add(0, id, 0, string)
|
|
}
|
|
|
|
(popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true)
|
|
|
|
popup.setOnMenuItemClickListener {
|
|
it.onMenuItemClick()
|
|
true
|
|
}
|
|
|
|
popup.show()
|
|
return popup
|
|
}
|
|
} |