cloudstream/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt

483 lines
16 KiB
Kotlin
Raw Normal View History

package com.lagradost.cloudstream3.utils
2021-05-12 21:51:02 +00:00
2021-05-20 21:25:41 +00:00
import android.Manifest
2021-06-15 23:25:58 +00:00
import android.annotation.SuppressLint
2021-05-12 21:51:02 +00:00
import android.app.Activity
2021-06-10 23:00:22 +00:00
import android.app.AppOpsManager
2021-12-12 02:33:17 +00:00
import android.app.Dialog
2021-05-28 13:38:06 +00:00
import android.content.Context
2021-05-20 21:25:41 +00:00
import android.content.pm.PackageManager
2021-12-13 18:41:33 +00:00
import android.content.res.Configuration
2021-05-15 23:37:42 +00:00
import android.content.res.Resources
2021-06-14 00:00:29 +00:00
import android.graphics.Color
2021-05-28 13:38:06 +00:00
import android.os.Build
2021-09-20 21:11:36 +00:00
import android.os.Bundle
2022-04-02 01:38:55 +00:00
import android.view.*
2021-06-10 23:00:22 +00:00
import android.view.inputmethod.InputMethodManager
2021-08-19 20:05:18 +00:00
import android.widget.ImageView
2022-04-02 01:38:55 +00:00
import android.widget.ListAdapter
import android.widget.ListView
2021-10-09 21:59:37 +00:00
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
2021-10-09 21:59:37 +00:00
import androidx.annotation.IdRes
2021-06-15 23:25:58 +00:00
import androidx.appcompat.view.ContextThemeWrapper
import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu
2021-05-20 21:25:41 +00:00
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
2021-07-19 13:19:47 +00:00
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
2021-06-06 18:06:01 +00:00
import androidx.fragment.app.FragmentActivity
2021-09-20 21:11:36 +00:00
import androidx.navigation.fragment.NavHostFragment
2021-05-15 23:37:42 +00:00
import androidx.preference.PreferenceManager
import com.bumptech.glide.load.engine.DiskCacheStrategy
2021-08-19 20:05:18 +00:00
import com.bumptech.glide.load.model.GlideUrl
2022-01-24 20:39:22 +00:00
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.lagradost.cloudstream3.R
2021-09-01 13:18:41 +00:00
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
2021-10-30 18:14:12 +00:00
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.GlideOptions.bitmapTransform
import jp.wasabeef.glide.transformations.BlurTransformation
2021-06-14 00:00:29 +00:00
import kotlin.math.roundToInt
2021-05-12 21:51:02 +00:00
object UIHelper {
2021-05-15 23:37:42 +00:00
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)
2021-05-20 21:25:41 +00:00
fun Activity.checkWrite(): Boolean {
2021-07-24 15:13:21 +00:00
return (ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
2022-12-26 19:55:23 +00:00
== 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)
2021-05-20 21:25:41 +00:00
}
fun Activity.requestRW() {
2021-07-24 15:13:21 +00:00
ActivityCompat.requestPermissions(
this,
2021-05-20 21:25:41 +00:00
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
2022-08-07 07:28:21 +00:00
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MANAGE_EXTERNAL_STORAGE
2021-05-20 21:25:41 +00:00
),
2021-07-24 15:13:21 +00:00
1337
)
2021-05-20 21:25:41 +00:00
}
2022-04-02 01:38:55 +00:00
/**
* 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()
}
2022-01-24 20:39:22 +00:00
fun Activity?.getSpanCount(): Int? {
2022-04-25 17:34:14 +00:00
val compactView = false
2021-12-13 18:41:33 +00:00
val spanCountLandscape = if (compactView) 2 else 6
val spanCountPortrait = if (compactView) 1 else 3
2022-04-25 17:34:14 +00:00
val orientation = this?.resources?.configuration?.orientation ?: return null
2021-12-13 18:41:33 +00:00
return if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
spanCountLandscape
} else {
spanCountPortrait
}
}
fun Fragment.hideKeyboard() {
2021-08-04 13:30:34 +00:00
activity?.window?.decorView?.clearFocus()
2021-11-12 16:55:54 +00:00
view?.let {
hideKeyboard(it)
}
}
fun Activity.hideKeyboard() {
window?.decorView?.clearFocus()
this.findViewById<View>(android.R.id.content)?.rootView?.let {
hideKeyboard(it)
}
}
2021-10-09 21:59:37 +00:00
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) {
2022-08-25 01:59:20 +00:00
logError(t)
2021-09-20 21:11:36 +00:00
}
}
2021-07-19 13:19:47 +00:00
@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 {
2022-02-05 01:05:13 +00:00
if (this == null || url.isNullOrBlank()) return false
2022-02-05 01:05:13 +00:00
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()
2022-02-05 01:05:13 +00:00
true
2021-10-09 21:59:37 +00:00
} catch (e: Exception) {
2021-09-01 13:18:41 +00:00
logError(e)
2022-02-05 01:05:13 +00:00
false
2021-08-19 20:05:18 +00:00
}
}
2022-04-13 17:29:30 +00:00
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)
2022-04-13 17:29:30 +00:00
.load(GlideUrl(url) { headers ?: emptyMap() })
.apply(bitmapTransform(BlurTransformation(radius, sample)))
2022-01-24 20:39:22 +00:00
.transition(
DrawableTransitionOptions.withCrossFade()
)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(this)
res.clearOnDetach()
} catch (e: Exception) {
logError(e)
}
}
2021-06-14 00:00:29 +00:00
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
}
2021-06-06 18:06:01 +00:00
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
2021-06-10 19:43:05 +00:00
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
2021-07-24 15:13:21 +00:00
// 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
2021-06-06 18:06:01 +00:00
// or View.SYSTEM_UI_FLAG_LOW_PROFILE
)
// window.addFlags(View.KEEP_SCREEN_ON)
}
2021-07-24 15:13:21 +00:00
2021-06-10 18:21:42 +00:00
fun FragmentActivity.popCurrentPage() {
2021-09-20 21:11:36 +00:00
this.onBackPressed()
/*val currentFragment = supportFragmentManager.fragments.lastOrNull {
2021-06-10 18:21:42 +00:00
it.isVisible
} ?: return
2021-06-06 18:06:01 +00:00
2021-06-10 18:21:42 +00:00
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.enter_anim,
R.anim.exit_anim,
R.anim.pop_enter,
R.anim.pop_exit
)
.remove(currentFragment)
2021-09-20 21:11:36 +00:00
.commitAllowingStateLoss()*/
2021-06-10 18:21:42 +00:00
}
/*
2021-06-06 18:06:01 +00:00
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()
}
}
2021-06-10 18:21:42 +00:00
}*/
2021-06-06 18:06:01 +00:00
fun Context.getStatusBarHeight(): Int {
2022-01-24 20:39:22 +00:00
if (isTvSettings()) {
2021-10-30 18:14:12 +00:00
return 0
}
var result = 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
result = resources.getDimensionPixelSize(resourceId)
}
return result
}
2022-05-15 18:38:32 +00:00
fun Context?.fixPaddingStatusbar(v: View?) {
if (v == null || this == null) return
2022-01-24 20:39:22 +00:00
v.setPadding(
v.paddingLeft,
v.paddingTop + getStatusBarHeight(),
v.paddingRight,
v.paddingBottom
)
}
2021-10-09 21:59:37 +00:00
2022-11-02 22:55:41 +00:00
fun Context.fixPaddingStatusbarView(v: View?) {
if (v == null) return
2021-10-22 13:23:48 +00:00
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
}
2022-04-25 17:34:14 +00:00
fun Context?.IsBottomLayout(): Boolean {
if (this == null) return true
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
2022-04-25 17:34:14 +00:00
return settingsManager.getBoolean(getString(R.string.bottom_title_key), true)
}
2021-06-06 18:06:01 +00:00
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 =
2022-04-02 01:38:55 +00:00
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
changeStatusBarState(isEmulatorSettings())
2021-06-06 18:06:01 +00:00
// window.clearFlags(View.KEEP_SCREEN_ON)
}
2021-06-10 23:00:22 +00:00
fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {
2021-12-12 02:33:17 +00:00
return try {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
2022-01-24 20:39:22 +00:00
settingsManager?.getBoolean(
getString(R.string.pip_enabled_key),
true
) ?: true && isInPlayer
} catch (e: Exception) {
2021-12-12 02:33:17 +00:00
logError(e)
false
}
2021-06-10 23:00:22 +00:00
}
fun Context.hasPIPPermission(): Boolean {
val appOps =
getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
2022-04-10 13:57:02 +00:00
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
}
2021-06-10 23:00:22 +00:00
}
2022-04-30 01:13:50 +00:00
fun hideKeyboard(view: View?) {
2022-05-15 18:38:32 +00:00
if (view == null) return
2022-04-30 01:13:50 +00:00
2022-01-24 20:39:22 +00:00
val inputMethodManager =
view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?
2021-10-30 18:14:12 +00:00
inputMethodManager?.hideSoftInputFromWindow(view.windowToken, 0)
}
fun showInputMethod(view: View?) {
2022-01-24 20:39:22 +00:00
if (view == null) return
val inputMethodManager =
view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager?
2021-10-30 18:14:12 +00:00
inputMethodManager?.showSoftInput(view, 0)
2021-06-10 23:00:22 +00:00
}
2021-06-15 23:25:58 +00:00
2021-12-12 02:33:17 +00:00
fun Dialog?.dismissSafe(activity: Activity?) {
if (this?.isShowing == true && activity?.isFinishing == false) {
this.dismiss()
}
}
2021-12-26 00:05:10 +00:00
fun Dialog?.dismissSafe() {
if (this?.isShowing == true) {
this.dismiss()
}
}
2021-07-24 15:13:21 +00:00
/**id, stringRes */
@SuppressLint("RestrictedApi")
fun View.popupMenuNoIcons(
2021-06-15 23:25:58 +00:00
items: List<Pair<Int, Int>>,
2021-07-24 15:13:21 +00:00
onMenuItemClick: MenuItem.() -> Unit,
2021-06-15 23:25:58 +00:00
): 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
}
2021-06-26 22:15:19 +00:00
2021-07-24 15:13:21 +00:00
/**id, string */
@SuppressLint("RestrictedApi")
2021-07-25 20:50:16 +00:00
fun View.popupMenuNoIconsAndNoStringRes(
2021-06-26 22:15:19 +00:00
items: List<Pair<Int, String>>,
2021-07-24 15:13:21 +00:00
onMenuItemClick: MenuItem.() -> Unit,
2021-06-26 22:15:19 +00:00
): PopupMenu {
val ctw = ContextThemeWrapper(context, R.style.PopupMenu)
val popup = PopupMenu(ctw, this, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0)
2021-07-24 15:13:21 +00:00
items.forEach { (id, string) ->
popup.menu.add(0, id, 0, string)
2021-06-26 22:15:19 +00:00
}
(popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true)
popup.setOnMenuItemClickListener {
it.onMenuItemClick()
true
}
popup.show()
return popup
}
2021-05-12 21:51:02 +00:00
}