mirror of https://github.com/deniscerri/ytdlnis
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
332 lines
11 KiB
Kotlin
332 lines
11 KiB
Kotlin
package com.deniscerri.ytdlnis.util
|
|
|
|
import android.animation.ValueAnimator
|
|
import android.annotation.SuppressLint
|
|
import android.app.Dialog
|
|
import android.content.Context
|
|
import android.content.res.Resources
|
|
import android.graphics.Color
|
|
import android.graphics.Outline
|
|
import android.graphics.drawable.ShapeDrawable
|
|
import android.graphics.drawable.shapes.OvalShape
|
|
import android.media.MediaMetadataRetriever
|
|
import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION
|
|
import android.net.Uri
|
|
import android.util.DisplayMetrics
|
|
import android.util.TypedValue
|
|
import android.view.MotionEvent
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import android.view.ViewOutlineProvider
|
|
import android.view.animation.Interpolator
|
|
import android.widget.EditText
|
|
import android.widget.ImageView
|
|
import android.widget.TextView
|
|
import androidx.annotation.Px
|
|
import androidx.core.view.updateLayoutParams
|
|
import androidx.lifecycle.lifecycleScope
|
|
import androidx.lifecycle.withStarted
|
|
import androidx.recyclerview.widget.RecyclerView
|
|
import com.deniscerri.ytdlnis.App
|
|
import com.deniscerri.ytdlnis.R
|
|
import com.deniscerri.ytdlnis.database.repository.DownloadRepository
|
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
import com.google.android.material.tabs.TabLayout
|
|
import com.neo.highlight.core.Highlight
|
|
import com.neo.highlight.util.listener.HighlightTextWatcher
|
|
import com.neo.highlight.util.scheme.ColorScheme
|
|
import com.squareup.picasso.Picasso
|
|
import kotlinx.coroutines.launch
|
|
import me.zhanghai.android.fastscroll.FastScrollerBuilder
|
|
import org.json.JSONObject
|
|
import java.io.File
|
|
import java.net.HttpCookie
|
|
import java.util.Locale
|
|
import java.util.regex.Pattern
|
|
import kotlin.math.abs
|
|
|
|
|
|
object Extensions {
|
|
fun dp(resources: Resources, px: Float) : Int {
|
|
val metrics: DisplayMetrics = resources.displayMetrics
|
|
return (metrics.density * px).toInt()
|
|
}
|
|
|
|
private var textHighLightSchemes = listOf(
|
|
ColorScheme(Pattern.compile("([\"'])(?:\\\\1|.)*?\\1"), Color.parseColor("#FC8500")),
|
|
ColorScheme(Pattern.compile("yt-dlp"), Color.parseColor("#77eb09")),
|
|
ColorScheme(Pattern.compile("(https?://(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?://(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})"), Color.parseColor("#b5942f")),
|
|
ColorScheme(Pattern.compile("\\d+(\\.\\d)?%"), Color.parseColor("#43a564"))
|
|
)
|
|
|
|
fun View.enableTextHighlight(){
|
|
if (this is EditText || this is TextView){
|
|
//init syntax highlighter
|
|
val highlight = Highlight()
|
|
val highlightWatcher = HighlightTextWatcher()
|
|
|
|
highlight.addScheme(
|
|
*textHighLightSchemes.map { it }.toTypedArray()
|
|
)
|
|
highlightWatcher.addScheme(
|
|
*textHighLightSchemes.map { it }.toTypedArray()
|
|
)
|
|
|
|
highlight.setSpan(this as TextView)
|
|
this.addTextChangedListener(highlightWatcher)
|
|
}
|
|
}
|
|
|
|
fun EditText.setTextAndRecalculateWidth(t : String){
|
|
val scale = context.resources.displayMetrics.density
|
|
this.setText(t)
|
|
val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
this.measure(widthMeasureSpec, heightMeasureSpec)
|
|
val requiredWidth: Int = this.measuredWidth
|
|
if (t.length < 5){
|
|
this.layoutParams.width = (70 * scale + 0.5F).toInt()
|
|
}else{
|
|
this.layoutParams.width = requiredWidth
|
|
}
|
|
this.requestLayout()
|
|
}
|
|
|
|
|
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
fun RecyclerView.forceFastScrollMode()
|
|
{
|
|
overScrollMode = View.OVER_SCROLL_ALWAYS
|
|
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
|
isVerticalScrollBarEnabled = true
|
|
setOnTouchListener { view, event ->
|
|
if (event.x >= this.width - 30) {
|
|
view.parent.requestDisallowInterceptTouchEvent(true)
|
|
when (event.action and MotionEvent.ACTION_MASK) {
|
|
MotionEvent.ACTION_UP -> view.parent.requestDisallowInterceptTouchEvent(false)
|
|
}
|
|
}
|
|
false
|
|
}
|
|
}
|
|
|
|
fun RecyclerView.enableFastScroll(){
|
|
val drawable = ShapeDrawable(OvalShape())
|
|
drawable.paint.color = context.getColor(android.R.color.transparent)
|
|
|
|
FastScrollerBuilder(this)
|
|
.useMd2Style()
|
|
.setTrackDrawable(drawable)
|
|
.build()
|
|
}
|
|
|
|
fun File.getMediaDuration(context: Context): Int {
|
|
return kotlin.runCatching {
|
|
if (!exists()) return 0
|
|
val retriever = MediaMetadataRetriever()
|
|
retriever.setDataSource(context, Uri.parse(absolutePath))
|
|
val duration = retriever.extractMetadata(METADATA_KEY_DURATION)
|
|
retriever.release()
|
|
|
|
duration?.toIntOrNull()?.div(1000) ?: 0
|
|
}.getOrElse { 0 }
|
|
}
|
|
|
|
|
|
fun Dialog.setFullScreen(
|
|
@Px cornerRadius: Int = 0,
|
|
skipCollapsed: Boolean = true
|
|
) {
|
|
check(this is BottomSheetDialog) {
|
|
"Dialog must be a BottomSheetBottomSheetDialog."
|
|
}
|
|
|
|
lifecycleScope.launch {
|
|
withStarted {
|
|
val bottomSheetLayout = findViewById<ViewGroup>(com.google.android.material.R.id.design_bottom_sheet) ?: return@withStarted
|
|
with(bottomSheetLayout) {
|
|
updateLayoutParams {
|
|
height = ViewGroup.LayoutParams.MATCH_PARENT
|
|
width = ViewGroup.LayoutParams.MATCH_PARENT
|
|
}
|
|
clipToOutline = true
|
|
outlineProvider = object : ViewOutlineProvider() {
|
|
override fun getOutline(view: View, outline: Outline) {
|
|
outline.setRoundRect(
|
|
0,
|
|
0,
|
|
view.width,
|
|
view.height + cornerRadius,
|
|
cornerRadius.toFloat()
|
|
)
|
|
}
|
|
}
|
|
}
|
|
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
|
behavior.maxWidth = ViewGroup.LayoutParams.MATCH_PARENT
|
|
behavior.skipCollapsed = skipCollapsed
|
|
}
|
|
}
|
|
}
|
|
|
|
fun JSONObject.getIntByAny(vararg tags:String): Int {
|
|
tags.forEach {
|
|
runCatching {
|
|
return this.getInt(it)
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
fun JSONObject.getStringByAny(vararg tags:String): String {
|
|
tags.forEach {
|
|
runCatching {
|
|
val tmp = this.getString(it)
|
|
if (tmp != "null") return tmp
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
fun TextView.setCustomTextSize(newSize: Float){
|
|
this.setTextSize(TypedValue.COMPLEX_UNIT_SP, newSize)
|
|
}
|
|
|
|
fun Int.toStringDuration(locale: Locale): String {
|
|
var format = String.format(
|
|
locale,
|
|
"%02d:%02d:%02d",
|
|
this / 3600,
|
|
this % 3600 / 60,
|
|
this % 60
|
|
)
|
|
// 00:00:00
|
|
if (this < 600) format = format.substring(4) else if (this < 3600) format =
|
|
format.substring(3) else if (this < 36000) format = format.substring(1)
|
|
return format
|
|
}
|
|
fun View.popup(){
|
|
val animator = ValueAnimator.ofFloat( 0.75f, 1f)
|
|
animator.addUpdateListener { animation: ValueAnimator ->
|
|
val value = animation.animatedValue as Float
|
|
this.scaleX = value
|
|
this.scaleY = value
|
|
}
|
|
animator.interpolator = Extensions.CustomInterpolator()
|
|
animator.setDuration(300)
|
|
animator.start()
|
|
}
|
|
|
|
fun TabLayout.Tab.createBadge(nr: Int){
|
|
removeBadge()
|
|
if (nr > 0) {
|
|
orCreateBadge.apply {
|
|
number = nr
|
|
verticalOffset = 3
|
|
horizontalOffset =
|
|
if (nr < 10) dp(App.instance.resources, 7f)
|
|
else if (nr < 100) dp(App.instance.resources, 10f)
|
|
else dp(App.instance.resources, 20f)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun String.appendLineToLog(line: String): String {
|
|
val lines = this.split("\n")
|
|
if (!lines.takeLast(3).contains(line)){
|
|
//clean dublicate progress + add newline
|
|
var newLine = line
|
|
if (newLine.contains("[download")) {
|
|
newLine = "[download]" + line.split("[download]").last()
|
|
}
|
|
|
|
return lines.dropLastWhile { it.contains("[download") }.joinToString("\n") + "\n${newLine}"
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
fun ImageView.loadThumbnail(hideThumb: Boolean, imageURL: String){
|
|
if(!hideThumb){
|
|
if (imageURL.isNotEmpty()) {
|
|
Picasso.get()
|
|
.load(imageURL)
|
|
.resize(1280, 0)
|
|
.onlyScaleDown()
|
|
.into(this)
|
|
|
|
} else {
|
|
Picasso.get().load(R.color.black).into(this)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
fun List<DownloadRepository.Status>.toListString() : List<String>{
|
|
return this.map { it.toString() }
|
|
}
|
|
|
|
fun List<String>.closestValue(value: String) = minBy { abs(value.toInt() - it.toInt()) }
|
|
|
|
class CustomInterpolator : Interpolator {
|
|
override fun getInterpolation(input: Float): Float {
|
|
// Adjust this curve as needed for desired animation feel
|
|
return (Math.pow((input - 1).toDouble(), 5.0) + 1).toFloat()
|
|
}
|
|
}
|
|
|
|
fun String.convertToTimestamp() : Int {
|
|
return try {
|
|
val timeArray = this.split(":")
|
|
var timeSeconds = timeArray[timeArray.lastIndex].toInt()
|
|
var times = 60
|
|
for (i in timeArray.lastIndex - 1 downTo 0) {
|
|
timeSeconds += timeArray[i].toInt() * times
|
|
times *= 60
|
|
}
|
|
timeSeconds
|
|
}catch (e: Exception){
|
|
e.printStackTrace()
|
|
0
|
|
}
|
|
}
|
|
|
|
fun String.convertNetscapeToSetCookie(): String {
|
|
// Split the Netscape cookie string
|
|
val parts =
|
|
this.split("\t").dropLastWhile { it.isEmpty() }
|
|
.toTypedArray()
|
|
|
|
if (parts.isEmpty()) return ""
|
|
|
|
// Extract the individual components
|
|
val domain = parts[0].trim { it <= ' ' }
|
|
val isSecure =
|
|
java.lang.Boolean.parseBoolean(parts[3].trim { it <= ' ' })
|
|
val expiry = parts[4].trim { it <= ' ' }.toLong()
|
|
val name = parts[5].trim { it <= ' ' }
|
|
val value = parts[6].trim { it <= ' ' }
|
|
|
|
// Create the BasicClientCookie
|
|
val cookie = HttpCookie(name, value)
|
|
cookie.domain = domain
|
|
cookie.path = "/"
|
|
cookie.secure = isSecure
|
|
|
|
// Set expiry
|
|
if (expiry != 0L) {
|
|
cookie.maxAge = expiry
|
|
} else {
|
|
// For session cookies, set to null
|
|
cookie.maxAge = -1L
|
|
}
|
|
|
|
// Get the Set-Cookie header format
|
|
return cookie.toString()
|
|
}
|
|
|
|
|
|
} |