AquaStream/app/src/main/java/com/lagradost/cloudstream3/UIHelper.kt

456 lines
16 KiB
Kotlin

package com.lagradost.cloudstream3
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.AppOpsManager
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Color
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import android.view.Gravity
import android.view.MenuItem
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity
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.preference.PreferenceManager
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.common.wrappers.Wrappers.packageManager
import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.utils.Event
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)
}
fun Activity.requestRW() {
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
),
1337
)
}
fun Fragment.hideKeyboard() {
view.let {
if (it != null) {
activity?.hideKeyboard(it)
}
}
}
@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 AppCompatActivity.loadResult(url: String, slug: String, apiName: String) {
this.runOnUiThread {
viewModelStore.clear()
this.supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit)
.add(R.id.homeRoot, ResultFragment.newInstance(url, slug, apiName))
.commit()
}
}
fun Activity?.loadSearchResult(card : SearchResponse) {
(this as AppCompatActivity?)?.loadResult(card.url, card.slug, card.apiName)
}
fun Context.getStatusBarHeight(): Int {
var result = 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
result = resources.getDimensionPixelSize(resourceId)
}
return result
}
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.fixPaddingStatusbar(v: View) {
v.setPadding(v.paddingLeft, v.paddingTop + getStatusBarHeight(), v.paddingRight, v.paddingBottom)
}
private fun Context.getGridFormat(): String {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
return settingsManager.getString(getString(R.string.grid_format_key), "grid")!!
}
fun Context.getGridFormatId(): Int {
return when (getGridFormat()) {
"list" -> R.layout.search_result_compact
"compact_list" -> R.layout.search_result_super_compact
else -> R.layout.search_result_grid
}
}
fun Context.getGridIsCompact(): Boolean {
return getGridFormat() != "grid"
}
fun Activity.requestLocalAudioFocus(focusRequest: AudioFocusRequest?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && focusRequest != null) {
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.requestAudioFocus(focusRequest)
} else {
val audioManager: AudioManager =
getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioManager.requestAudioFocus(
null,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
)
}
}
private var currentAudioFocusRequest: AudioFocusRequest? = null
private var currentAudioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null
var onAudioFocusEvent = Event<Boolean>()
private fun getAudioListener(): AudioManager.OnAudioFocusChangeListener? {
if (currentAudioFocusChangeListener != null) return currentAudioFocusChangeListener
currentAudioFocusChangeListener = AudioManager.OnAudioFocusChangeListener {
onAudioFocusEvent.invoke(
when (it) {
AudioManager.AUDIOFOCUS_GAIN -> false
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE -> false
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT -> false
else -> true
}
)
}
return currentAudioFocusChangeListener
}
fun Context.isCastApiAvailable(): Boolean {
val isCastApiAvailable =
GoogleApiAvailability.getInstance()
.isGooglePlayServicesAvailable(applicationContext) == ConnectionResult.SUCCESS
try {
applicationContext?.let { CastContext.getSharedInstance(it) }
} catch (e: Exception) {
println(e)
// track non-fatal
return false
}
return isCastApiAvailable
}
fun Context.isConnectedToChromecast(): Boolean {
if (isCastApiAvailable()) {
val castContext = CastContext.getSharedInstance(this)
if (castContext.castState == CastState.CONNECTED) {
return true
}
}
return false
}
fun Context.isUsingMobileData(): Boolean {
val conManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = conManager.allNetworks
return networkInfo.any {
conManager.getNetworkCapabilities(it)?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true
}
}
fun Context.isAppInstalled(uri: String): Boolean {
val pm = packageManager(this)
var appInstalled = false
appInstalled = try {
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES)
true
} catch (e: PackageManager.NameNotFoundException) {
false
}
return appInstalled
}
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 getFocusRequest(): AudioFocusRequest? {
if (currentAudioFocusRequest != null) return currentAudioFocusRequest
currentAudioFocusRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
setAudioAttributes(AudioAttributes.Builder().run {
setUsage(AudioAttributes.USAGE_MEDIA)
setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
build()
})
setAcceptsDelayedFocusGain(true)
getAudioListener()?.let {
setOnAudioFocusChangeListener(it)
}
build()
}
} else {
null
}
return currentAudioFocusRequest
}
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() {
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 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
) // or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
// window.clearFlags(View.KEEP_SCREEN_ON)
}
fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
return settingsManager?.getBoolean("pip_enabled", true) ?: true && isInPlayer
}
fun Context.hasPIPPermission(): Boolean {
val appOps =
getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
return appOps.checkOpNoThrow(
AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
android.os.Process.myUid(),
packageName
) == AppOpsManager.MODE_ALLOWED
}
fun Context.hideKeyboard(view: View) {
val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
}
/**id, icon, stringRes */
@SuppressLint("RestrictedApi")
fun View.popupMenu(
items: List<Triple<Int, 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, icon, stringRes) ->
popup.menu.add(0, id, 0, stringRes).setIcon(icon)
}
(popup.menu as? MenuBuilder)?.setOptionalIconsVisible(true)
popup.setOnMenuItemClickListener {
it.onMenuItemClick()
true
}
popup.show()
return popup
}
/**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
}
}