2021-04-22 11:47:18 +02:00
|
|
|
package org.pixeldroid.app.utils
|
2020-05-14 20:14:41 +02:00
|
|
|
|
2021-09-18 13:11:10 +02:00
|
|
|
import android.content.*
|
2020-12-26 12:10:54 +01:00
|
|
|
import android.content.res.Resources
|
2021-09-18 13:11:10 +02:00
|
|
|
import android.graphics.Bitmap
|
2022-07-10 13:42:19 +02:00
|
|
|
import android.graphics.Color
|
2021-09-18 13:11:10 +02:00
|
|
|
import android.graphics.ImageDecoder
|
2021-09-23 22:35:20 +02:00
|
|
|
import android.graphics.Matrix
|
2020-05-14 20:14:41 +02:00
|
|
|
import android.net.ConnectivityManager
|
2020-12-28 17:18:07 +01:00
|
|
|
import android.net.Uri
|
2020-12-26 12:10:54 +01:00
|
|
|
import android.os.Build
|
2021-09-18 13:11:10 +02:00
|
|
|
import android.provider.MediaStore
|
2021-02-08 18:01:48 +01:00
|
|
|
import android.util.DisplayMetrics
|
2022-07-10 13:42:19 +02:00
|
|
|
import android.util.TypedValue
|
2021-02-08 18:01:48 +01:00
|
|
|
import android.view.WindowManager
|
2022-07-10 13:42:19 +02:00
|
|
|
import androidx.annotation.AttrRes
|
|
|
|
import androidx.annotation.ColorInt
|
|
|
|
import androidx.annotation.StyleRes
|
2020-12-26 12:10:54 +01:00
|
|
|
import androidx.appcompat.app.AppCompatDelegate
|
2020-12-28 17:18:07 +01:00
|
|
|
import androidx.browser.customtabs.CustomTabsIntent
|
2021-09-23 22:35:20 +02:00
|
|
|
import androidx.exifinterface.media.ExifInterface
|
2021-01-13 01:28:08 +01:00
|
|
|
import androidx.fragment.app.Fragment
|
|
|
|
import androidx.lifecycle.DefaultLifecycleObserver
|
|
|
|
import androidx.lifecycle.LifecycleOwner
|
2022-07-10 13:42:19 +02:00
|
|
|
import androidx.preference.PreferenceManager
|
2021-05-22 13:03:13 +02:00
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
|
|
|
import androidx.recyclerview.widget.RecyclerView
|
2022-06-18 22:21:19 +02:00
|
|
|
import com.arthenica.ffmpegkit.FFmpegKitConfig
|
2022-07-10 13:42:19 +02:00
|
|
|
import com.google.android.material.color.MaterialColors
|
2021-02-04 20:44:31 +01:00
|
|
|
import okhttp3.HttpUrl
|
2021-09-23 22:35:20 +02:00
|
|
|
import org.pixeldroid.app.R
|
2021-01-13 01:28:08 +01:00
|
|
|
import kotlin.properties.ReadWriteProperty
|
|
|
|
import kotlin.reflect.KProperty
|
2020-05-14 20:14:41 +02:00
|
|
|
|
2020-12-26 12:10:54 +01:00
|
|
|
fun hasInternet(context: Context): Boolean {
|
|
|
|
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
|
|
return cm.activeNetwork != null
|
|
|
|
}
|
|
|
|
|
2021-02-04 20:44:31 +01:00
|
|
|
/**
|
|
|
|
* Check if domain is valid or not
|
|
|
|
*/
|
|
|
|
fun validDomain(domain: String?): Boolean {
|
|
|
|
domain?.apply {
|
|
|
|
try {
|
|
|
|
HttpUrl.Builder().host(replace("https://", "")).scheme("https").build()
|
|
|
|
} catch (e: IllegalArgumentException) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} ?: return false
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-06-19 14:28:00 +02:00
|
|
|
fun Uri.fileExtension(contentResolver: ContentResolver): String? {
|
|
|
|
return if (scheme == "content") {
|
|
|
|
contentResolver.getType(this)?.takeLastWhile { it != '/' }
|
|
|
|
} else {
|
|
|
|
toString().takeLastWhile { it != '/' }
|
|
|
|
}
|
|
|
|
}
|
2021-02-08 18:01:48 +01:00
|
|
|
|
|
|
|
fun Context.displayDimensionsInPx(): Pair<Int, Int> {
|
|
|
|
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
|
|
|
|
|
|
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
|
|
Pair(windowManager.currentWindowMetrics.bounds.width(), windowManager.currentWindowMetrics.bounds.height())
|
|
|
|
} else {
|
|
|
|
val metrics = DisplayMetrics()
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
windowManager.defaultDisplay.getMetrics(metrics)
|
|
|
|
Pair(metrics.widthPixels, metrics.heightPixels)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-26 12:10:54 +01:00
|
|
|
fun normalizeDomain(domain: String): String {
|
|
|
|
return "https://" + domain
|
|
|
|
.replace("http://", "")
|
|
|
|
.replace("https://", "")
|
|
|
|
.trim(Char::isWhitespace)
|
|
|
|
}
|
|
|
|
|
2022-06-18 22:21:19 +02:00
|
|
|
fun Context.ffmpegSafeUri(inputUri: Uri?): String =
|
|
|
|
if (inputUri?.scheme == "content")
|
|
|
|
FFmpegKitConfig.getSafParameterForRead(this, inputUri)
|
|
|
|
else inputUri.toString()
|
|
|
|
|
|
|
|
|
2021-09-18 13:11:10 +02:00
|
|
|
fun bitmapFromUri(contentResolver: ContentResolver, uri: Uri?): Bitmap =
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
|
|
ImageDecoder
|
|
|
|
.decodeBitmap(
|
|
|
|
ImageDecoder.createSource(contentResolver, uri!!)
|
|
|
|
)
|
|
|
|
{ decoder, _, _ -> decoder.isMutableRequired = true }
|
|
|
|
} else {
|
2022-02-16 13:01:32 +01:00
|
|
|
@Suppress("DEPRECATION")
|
2021-09-23 22:35:20 +02:00
|
|
|
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, uri)
|
|
|
|
modifyOrientation(bitmap!!, contentResolver, uri!!)
|
2021-09-18 13:11:10 +02:00
|
|
|
}
|
|
|
|
|
2021-09-23 22:35:20 +02:00
|
|
|
fun modifyOrientation(
|
|
|
|
bitmap: Bitmap,
|
|
|
|
contentResolver: ContentResolver,
|
|
|
|
uri: Uri
|
|
|
|
): Bitmap {
|
|
|
|
val inputStream = contentResolver.openInputStream(uri)!!
|
|
|
|
val ei = ExifInterface(inputStream)
|
|
|
|
return when (ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
|
|
|
|
ExifInterface.ORIENTATION_ROTATE_90 -> bitmap.rotate(90f)
|
|
|
|
ExifInterface.ORIENTATION_ROTATE_180 -> bitmap.rotate(180f)
|
|
|
|
ExifInterface.ORIENTATION_ROTATE_270 -> bitmap.rotate(270f)
|
|
|
|
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> bitmap.flip(horizontal = true, vertical = false)
|
|
|
|
ExifInterface.ORIENTATION_FLIP_VERTICAL -> bitmap.flip(horizontal = false, vertical = true)
|
|
|
|
else -> bitmap
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun Bitmap.rotate(degrees: Float): Bitmap {
|
|
|
|
val matrix = Matrix()
|
|
|
|
matrix.postRotate(degrees)
|
|
|
|
return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun Bitmap.flip(horizontal: Boolean, vertical: Boolean): Bitmap {
|
|
|
|
val matrix = Matrix()
|
|
|
|
matrix.preScale(if (horizontal) -1f else 1f, if (vertical) -1f else 1f)
|
|
|
|
return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
|
|
|
|
}
|
|
|
|
|
2022-06-18 22:21:19 +02:00
|
|
|
fun BaseActivity.openUrl(url: String): Boolean {
|
2020-12-28 17:18:07 +01:00
|
|
|
|
|
|
|
val intent = CustomTabsIntent.Builder().build()
|
|
|
|
|
|
|
|
return try {
|
|
|
|
intent.launchUrl(this, Uri.parse(url))
|
|
|
|
true
|
|
|
|
} catch (e: ActivityNotFoundException) {
|
|
|
|
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
|
|
|
try {
|
|
|
|
startActivity(browserIntent)
|
|
|
|
true
|
|
|
|
} catch(e: ActivityNotFoundException) {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-26 12:10:54 +01:00
|
|
|
|
2021-05-22 13:03:13 +02:00
|
|
|
|
|
|
|
fun RecyclerView.limitedLengthSmoothScrollToPosition(targetItem: Int) {
|
|
|
|
layoutManager?.apply {
|
|
|
|
val maxScroll = 3
|
|
|
|
when (this) {
|
|
|
|
is LinearLayoutManager -> {
|
|
|
|
val topItem = findFirstVisibleItemPosition()
|
|
|
|
val distance = topItem - targetItem
|
|
|
|
val anchorItem = when {
|
|
|
|
distance > maxScroll -> targetItem + maxScroll
|
|
|
|
distance < -maxScroll -> targetItem - maxScroll
|
|
|
|
else -> topItem
|
|
|
|
}
|
|
|
|
if (anchorItem != topItem) scrollToPosition(anchorItem)
|
|
|
|
post {
|
|
|
|
smoothScrollToPosition(targetItem)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else -> smoothScrollToPosition(targetItem)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-26 12:10:54 +01:00
|
|
|
/**
|
|
|
|
* @brief Updates the application's theme depending on the given preferences and resources
|
|
|
|
*/
|
2022-07-10 13:42:19 +02:00
|
|
|
fun setThemeFromPreferences(preferences: SharedPreferences, resources: Resources) {
|
2020-12-26 12:10:54 +01:00
|
|
|
val themes = resources.getStringArray(R.array.theme_values)
|
|
|
|
//Set the theme
|
|
|
|
when(preferences.getString("theme", "")) {
|
|
|
|
//Light
|
|
|
|
themes[1] -> {
|
|
|
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
2020-05-14 20:14:41 +02:00
|
|
|
}
|
2020-12-26 12:10:54 +01:00
|
|
|
//Dark
|
|
|
|
themes[2] -> {
|
|
|
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
2020-05-19 09:49:34 +02:00
|
|
|
}
|
2020-12-26 12:10:54 +01:00
|
|
|
else -> {
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
|
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
|
|
|
} else {
|
|
|
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY)
|
2020-09-10 20:20:23 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-14 20:14:41 +02:00
|
|
|
}
|
2020-12-26 12:10:54 +01:00
|
|
|
}
|
2021-01-13 01:28:08 +01:00
|
|
|
|
2022-07-10 13:42:19 +02:00
|
|
|
@StyleRes
|
|
|
|
fun Context.themeNoActionBar(): Int {
|
|
|
|
return when(PreferenceManager.getDefaultSharedPreferences(this).getInt("themeColor", 0)) {
|
|
|
|
1 -> R.style.AppTheme2_NoActionBar
|
|
|
|
2 -> R.style.AppTheme3_NoActionBar
|
|
|
|
3 -> R.style.AppTheme4_NoActionBar
|
|
|
|
else -> R.style.AppTheme_NoActionBar
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@StyleRes
|
|
|
|
fun Context.themeActionBar(): Int {
|
|
|
|
return when(PreferenceManager.getDefaultSharedPreferences(this).getInt("themeColor", 0)) {
|
|
|
|
1 -> R.style.AppTheme2
|
|
|
|
2 -> R.style.AppTheme3
|
|
|
|
3 -> R.style.AppTheme4
|
|
|
|
else -> R.style.AppTheme
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@ColorInt
|
|
|
|
fun Context.getColorFromAttr(@AttrRes attrColor: Int): Int = MaterialColors.getColor(this, attrColor, Color.BLACK)
|
|
|
|
|
2021-01-13 01:28:08 +01:00
|
|
|
/**
|
|
|
|
* Delegated property to use in fragments to prevent memory leaks of bindings.
|
|
|
|
* This makes it unnecessary to set binding to null in onDestroyView.
|
|
|
|
* The value should be assigned in the Fragment's onCreateView()
|
|
|
|
*/
|
|
|
|
fun <T> Fragment.bindingLifecycleAware(): ReadWriteProperty<Fragment, T> =
|
|
|
|
object : ReadWriteProperty<Fragment, T>, DefaultLifecycleObserver {
|
|
|
|
|
|
|
|
private var binding: T? = null
|
|
|
|
|
|
|
|
override fun onDestroy(owner: LifecycleOwner) {
|
|
|
|
binding = null
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun getValue(thisRef: Fragment, property: KProperty<*>): T = binding!!
|
|
|
|
|
|
|
|
override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
|
|
|
|
binding = value
|
|
|
|
this@bindingLifecycleAware.viewLifecycleOwner.lifecycle.addObserver(this)
|
|
|
|
}
|
|
|
|
}
|