644 lines
22 KiB
Kotlin
644 lines
22 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.Bitmap
|
|
import android.graphics.Color
|
|
import android.graphics.drawable.Drawable
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import android.view.*
|
|
import android.view.ViewGroup.MarginLayoutParams
|
|
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.annotation.StyleRes
|
|
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.drawable.toBitmapOrNull
|
|
import androidx.core.graphics.green
|
|
import androidx.core.graphics.red
|
|
import androidx.core.view.WindowCompat
|
|
import androidx.core.view.WindowInsetsCompat
|
|
import androidx.core.view.WindowInsetsControllerCompat
|
|
import androidx.core.view.marginBottom
|
|
import androidx.core.view.marginLeft
|
|
import androidx.core.view.marginRight
|
|
import androidx.core.view.marginTop
|
|
import androidx.fragment.app.Fragment
|
|
import androidx.fragment.app.FragmentActivity
|
|
import androidx.navigation.fragment.NavHostFragment
|
|
import androidx.palette.graphics.Palette
|
|
import androidx.preference.PreferenceManager
|
|
import com.bumptech.glide.load.DataSource
|
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
import com.bumptech.glide.load.engine.GlideException
|
|
import com.bumptech.glide.load.model.GlideUrl
|
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
|
import com.bumptech.glide.request.RequestListener
|
|
import com.bumptech.glide.request.RequestOptions.bitmapTransform
|
|
import com.bumptech.glide.request.target.Target
|
|
import com.google.android.material.chip.Chip
|
|
import com.google.android.material.chip.ChipDrawable
|
|
import com.google.android.material.chip.ChipGroup
|
|
import com.lagradost.cloudstream3.CommonActivity.activity
|
|
import com.lagradost.cloudstream3.MainActivity
|
|
import com.lagradost.cloudstream3.R
|
|
import com.lagradost.cloudstream3.mvvm.logError
|
|
import com.lagradost.cloudstream3.ui.result.UiImage
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
|
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 Context.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 populateChips(view: ChipGroup?, tags: List<String>, @StyleRes style : Int = R.style.ChipFilled) {
|
|
if (view == null) return
|
|
view.removeAllViews()
|
|
val context = view.context ?: return
|
|
val maxTags = tags.take(10) // Limited because they are too much
|
|
|
|
maxTags.forEach { tag ->
|
|
val chip = Chip(context)
|
|
val chipDrawable = ChipDrawable.createFromAttributes(
|
|
context,
|
|
null,
|
|
0,
|
|
style
|
|
)
|
|
chip.setChipDrawable(chipDrawable)
|
|
chip.text = tag
|
|
chip.isChecked = false
|
|
chip.isCheckable = false
|
|
chip.isFocusable = false
|
|
chip.isClickable = false
|
|
chip.setTextColor(context.colorFromAttribute(R.attr.white))
|
|
view.addView(chip)
|
|
}
|
|
}
|
|
|
|
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 Context?.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) {
|
|
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment?
|
|
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
|
|
}
|
|
|
|
var createPaletteAsyncCache: HashMap<String, Palette> = hashMapOf()
|
|
fun createPaletteAsync(url: String, bitmap: Bitmap, callback: (Palette) -> Unit) {
|
|
createPaletteAsyncCache[url]?.let { palette ->
|
|
callback.invoke(palette)
|
|
return
|
|
}
|
|
Palette.from(bitmap).generate { paletteNull ->
|
|
paletteNull?.let { palette ->
|
|
createPaletteAsyncCache[url] = palette
|
|
callback(palette)
|
|
}
|
|
}
|
|
}
|
|
|
|
/*inline fun <reified T : ViewBinding> bindViewBinding(
|
|
inflater: LayoutInflater?,
|
|
container: ViewGroup?,
|
|
layout: Int
|
|
): Pair<T?, UiText?> {
|
|
return try {
|
|
val localInflater = inflater ?: container?.context?.let { LayoutInflater.from(it) }
|
|
?: return null to txt(
|
|
R.string.unable_to_inflate,
|
|
"Requires inflater OR container"
|
|
)//throw IllegalArgumentException("Requires inflater OR container"))
|
|
|
|
//println("methods: ${T::class.java.methods.map { it.name }}")
|
|
val bind = T::class.java.methods.first { it.name == "bind" }
|
|
//val inflate = T::class.java.methods.first { it.name == "inflate" }
|
|
val root = localInflater.inflate(layout, container, false)
|
|
bind.invoke(null, root) as T to null
|
|
} catch (t: Throwable) {
|
|
logError(t)
|
|
val message = txt(R.string.unable_to_inflate, t.message ?: "Primary constructor")
|
|
// if the desired layout is not found then we inflate the casted layout
|
|
/*try {
|
|
val localInflater = inflater ?: container?.context?.let { LayoutInflater.from(it) }
|
|
?: return null to txt(
|
|
R.string.unable_to_inflate,
|
|
"Requires inflater OR container"
|
|
)//throw IllegalArgumentException("Requires inflater OR container"))
|
|
|
|
// we don't know what method to use as there are 2, but first *should* always be true
|
|
return try {
|
|
val inflate = T::class.java.methods.first { it.name == "inflate" }
|
|
inflate.invoke(null, localInflater, container, false) as T
|
|
} catch (_: Throwable) {
|
|
val inflate = T::class.java.methods.last { it.name == "inflate" }
|
|
inflate.invoke(null, localInflater, container, false) as T
|
|
} to message
|
|
} catch (t: Throwable) {
|
|
logError(t)
|
|
}*/
|
|
|
|
null to message
|
|
}
|
|
}*/
|
|
|
|
fun ImageView?.setImage(
|
|
url: String?,
|
|
headers: Map<String, String>? = null,
|
|
@DrawableRes
|
|
errorImageDrawable: Int? = null,
|
|
fadeIn: Boolean = true,
|
|
radius: Int = 0,
|
|
sample: Int = 3,
|
|
colorCallback: ((Palette) -> Unit)? = null
|
|
): Boolean {
|
|
if (url.isNullOrBlank()) return false
|
|
this.setImage(
|
|
UiImage.Image(url, headers, errorImageDrawable),
|
|
errorImageDrawable,
|
|
fadeIn,
|
|
radius,
|
|
sample,
|
|
colorCallback
|
|
)
|
|
return true
|
|
}
|
|
|
|
fun ImageView?.setImage(
|
|
uiImage: UiImage?,
|
|
@DrawableRes
|
|
errorImageDrawable: Int? = null,
|
|
fadeIn: Boolean = true,
|
|
radius: Int = 0,
|
|
sample: Int = 3,
|
|
colorCallback: ((Palette) -> Unit)? = null,
|
|
): Boolean {
|
|
if (this == null || uiImage == null) return false
|
|
|
|
val (glideImage, identifier) =
|
|
(uiImage as? UiImage.Drawable)?.resId?.let {
|
|
it to it.toString()
|
|
} ?: (uiImage as? UiImage.Image)?.let { image ->
|
|
GlideUrl(image.url) { image.headers ?: emptyMap() } to image.url
|
|
} ?: return false
|
|
|
|
return try {
|
|
var builder = com.bumptech.glide.Glide.with(this)
|
|
.load(glideImage)
|
|
.skipMemoryCache(true)
|
|
.diskCacheStrategy(DiskCacheStrategy.ALL).let { req ->
|
|
if (fadeIn)
|
|
req.transition(DrawableTransitionOptions.withCrossFade())
|
|
else req
|
|
}
|
|
|
|
if (radius > 0) {
|
|
builder = builder.apply(bitmapTransform(BlurTransformation(radius, sample)))
|
|
}
|
|
|
|
if (colorCallback != null) {
|
|
builder = builder.listener(object : RequestListener<Drawable> {
|
|
|
|
override fun onResourceReady(
|
|
resource: Drawable,
|
|
model: Any,
|
|
target: Target<Drawable>?,
|
|
dataSource: DataSource,
|
|
isFirstResource: Boolean
|
|
): Boolean {
|
|
resource.toBitmapOrNull()
|
|
?.let { bitmap ->
|
|
createPaletteAsync(
|
|
identifier,
|
|
bitmap,
|
|
colorCallback
|
|
)
|
|
}
|
|
return false
|
|
}
|
|
|
|
override fun onLoadFailed(
|
|
e: GlideException?,
|
|
model: Any?,
|
|
target: Target<Drawable>,
|
|
isFirstResource: Boolean
|
|
): Boolean {
|
|
return false
|
|
}
|
|
})
|
|
}
|
|
|
|
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 = com.bumptech.glide.Glide.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
|
|
/** BUGGED AF **/
|
|
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
|
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
WindowInsetsControllerCompat(window, View(this)).let { controller ->
|
|
controller.hide(WindowInsetsCompat.Type.systemBars())
|
|
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
}
|
|
}*/
|
|
|
|
@Suppress("DEPRECATION")
|
|
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
|
|
)
|
|
//}
|
|
}
|
|
|
|
fun FragmentActivity.popCurrentPage() {
|
|
this.onBackPressedDispatcher.onBackPressed()
|
|
}
|
|
|
|
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 fixPaddingStatusbar(v: View?) {
|
|
if (v == null) return
|
|
val ctx = v.context ?: return
|
|
v.setPadding(
|
|
v.paddingLeft,
|
|
v.paddingTop + ctx.getStatusBarHeight(),
|
|
v.paddingRight,
|
|
v.paddingBottom
|
|
)
|
|
}
|
|
|
|
fun fixPaddingStatusbarMargin(v: View?) {
|
|
if (v == null) return
|
|
val ctx = v.context ?: return
|
|
|
|
v.layoutParams = v.layoutParams.apply {
|
|
if (this is MarginLayoutParams) {
|
|
setMargins(
|
|
v.marginLeft,
|
|
v.marginTop + ctx.getStatusBarHeight(),
|
|
v.marginRight,
|
|
v.marginBottom
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun fixPaddingStatusbarView(v: View?) {
|
|
if (v == null) return
|
|
val ctx = v.context ?: return
|
|
val params = v.layoutParams
|
|
params.height = ctx.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) {
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
window.insetsController?.hide(WindowInsets.Type.statusBars())
|
|
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
window.setFlags(
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
|
)
|
|
}
|
|
0
|
|
} else {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
window.insetsController?.show(WindowInsets.Type.statusBars())
|
|
|
|
} 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() {
|
|
|
|
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
|
|
WindowCompat.setDecorFitsSystemWindows(window, true)
|
|
WindowInsetsControllerCompat(window, View(this)).show(WindowInsetsCompat.Type.systemBars())
|
|
|
|
} else {*/ /** WINDOW COMPAT IS BUGGY DUE TO FU*KED UP PLAYER AND TRAILERS **/
|
|
Suppress("DEPRECATION")
|
|
window.decorView.systemUiVisibility =
|
|
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
|
//}
|
|
|
|
changeStatusBarState(isEmulatorSettings())
|
|
}
|
|
|
|
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 && activity?.isFinishing != 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
|
|
}
|
|
} |