Files
MixFile/app/src/main/java/com/donut/mixfile/util/CommonUtil.kt
2024-09-02 11:09:32 +08:00

355 lines
9.5 KiB
Kotlin

package com.donut.mixfile.util
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.OpenableColumns
import android.util.Log
import com.donut.mixfile.app
import com.donut.mixfile.appScope
import io.ktor.client.request.forms.FormBuilder
import io.ktor.http.Headers
import io.ktor.http.quote
import io.ktor.util.InternalAPI
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.net.NetworkInterface
import java.net.URL
import java.net.URLEncoder
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.math.log10
import kotlin.math.pow
fun String.copyToClipboard(showToast: Boolean = true) {
val clipboard = getClipBoard()
val clip = ClipData.newPlainText("Copied Text", this)
clipboard.setPrimaryClip(clip)
if (showToast) showToast("复制成功")
}
fun getClipBoard(context: Context = app.applicationContext): ClipboardManager {
return context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
}
fun readClipBoardText(): String {
val clipboard = getClipBoard()
val clip = clipboard.primaryClip
if (clip != null && clip.itemCount > 0) {
val text = clip.getItemAt(0).text
return text?.toString() ?: ""
}
return ""
}
fun formatFileSize(bytes: Long, mb: Boolean = false): String {
if (bytes <= 0) return "0 B"
if (mb) {
return String.format(
Locale.US,
"%.2f MB",
bytes / 1024.0 / 1024.0
)
}
val units = arrayOf("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
val digitGroups = (log10(bytes.toDouble()) / log10(1024.0)).toInt()
return String.format(
Locale.US,
"%.2f %s",
bytes / 1024.0.pow(digitGroups.toDouble()),
units.getOrNull(digitGroups)
)
}
fun getAppVersion(context: Context): Pair<String, Long> {
return try {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val versionName = packageInfo.versionName
val versionCode =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) packageInfo.longVersionCode else packageInfo.versionCode.toLong()
Pair(versionName, versionCode)
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
Pair("Unknown", -1L)
}
}
fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
class CachedDelegate<T>(val getKeys: () -> Array<Any?>, private val initializer: () -> T) {
private var cache: T? = null
private var keys: Array<Any?> = arrayOf()
operator fun getValue(thisRef: Any?, property: Any?): T {
val newKeys = getKeys()
if (cache == null || !keys.contentEquals(newKeys)) {
keys = newKeys
cache = initializer()
}
return cache!!
}
operator fun setValue(thisRef: Any?, property: Any?, value: T) {
cache = value
}
}
tailrec fun String.hashToMD5String(round: Int = 1): String {
val digest = hashMD5()
if (round > 1) {
return digest.toHex().hashToMD5String(round - 1)
}
return digest.toHex()
}
fun ByteArray.toHex(): String {
val sb = StringBuilder()
for (b in this) {
sb.append(String.format("%02x", b))
}
return sb.toString()
}
fun String.hashMD5() = calculateHash("MD5")
fun String.hashSHA256() = calculateHash("SHA-256")
fun String.calculateHash(algorithm: String): ByteArray {
val md = MessageDigest.getInstance(algorithm)
md.update(this.toByteArray())
return md.digest()
}
fun ByteArray.calculateHash(algorithm: String): String {
val md = MessageDigest.getInstance(algorithm)
md.update(this)
return md.digest().toHex()
}
fun ByteArray.hashSHA256() = calculateHash("SHA-256")
inline fun String.isUrl(block: (URL) -> Unit = {}): Boolean {
val urlPattern =
Regex("^https?://(www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)\$")
val result = urlPattern.matches(this)
if (result) {
ignoreError {
block(URL(this))
}
}
return result
}
fun getUrlHost(url: String): String? {
url.isUrl {
return it.host
}
return null
}
fun String.truncate(maxLength: Int): String {
return if (this.length > maxLength) {
this.substring(0, maxLength) + "..."
} else {
this
}
}
@OptIn(ExperimentalEncodingApi::class)
fun ByteArray.encodeToBase64() = Base64.encode(this)
@OptIn(ExperimentalEncodingApi::class)
fun String.decodeBase64() = Base64.decode(this)
@OptIn(ExperimentalEncodingApi::class)
fun String.decodeBase64String() = Base64.decode(this).decodeToString()
fun String.encodeToBase64() = this.toByteArray().encodeToBase64()
fun <T> List<T>.at(index: Long): T {
var fixedIndex = index % this.size
if (fixedIndex < 0) {
fixedIndex += this.size
}
return this[fixedIndex.toInt()]
}
fun <T> List<T>.at(index: Int): T {
return this.at(index.toLong())
}
infix fun <T> List<T>.elementEquals(other: List<T>): Boolean {
if (this.size != other.size) return false
val tracker = BooleanArray(this.size)
var counter = 0
root@ for (value in this) {
destination@ for ((i, o) in other.withIndex()) {
if (tracker[i]) {
continue@destination
} else if (value?.equals(o) == true) {
counter++
tracker[i] = true
continue@root
}
}
}
return counter == this.size
}
typealias UnitBlock = () -> Unit
fun debug(text: String?, tag: String = "test") {
Log.d(tag, text ?: "null")
}
fun String.encodeURL(): String? {
return URLEncoder.encode(this, "UTF-8")
}
fun String.getFileExtension(): String {
val index = this.lastIndexOf('.')
return if (index == -1) "" else this.substring(index + 1).lowercase()
}
inline fun catchError(tag: String = "", block: () -> Unit) {
try {
block()
} catch (e: Exception) {
showError(e, tag)
}
}
inline fun <T> ignoreError(tag: String = "", block: () -> T): T? {
try {
return block()
} catch (_: Exception) {
}
return null
}
fun getCurrentDate(reverseDays: Long = 0): String {
val formatter = SimpleDateFormat("yyyy-MM-dd", Locale.US)
return formatter.format(Date(System.currentTimeMillis() - (reverseDays * 86400 * 1000)))
}
fun getCurrentTime(): String {
val currentTime = Date()
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
return formatter.format(currentTime)
}
fun genRandomString(length: Int = 32): String {
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
return (1..length)
.map { kotlin.random.Random.nextInt(0, charPool.size) }
.map(charPool::get)
.joinToString("")
}
fun compressGzip(input: String): ByteArray {
val byteArrayOutputStream = ByteArrayOutputStream()
GZIPOutputStream(byteArrayOutputStream).use { gzip ->
gzip.write(input.toByteArray())
}
return byteArrayOutputStream.toByteArray()
}
fun decompressGzip(compressed: ByteArray): String {
val byteArrayInputStream = ByteArrayInputStream(compressed)
GZIPInputStream(byteArrayInputStream).use { gzip ->
return gzip.bufferedReader().use { it.readText() }
}
}
fun isValidUri(uriString: String): Boolean {
try {
val uri = Uri.parse(uriString)
return uri != null && uri.scheme != null
} catch (e: Exception) {
return false
}
}
fun showError(e: Throwable, tag: String = "") {
Log.e(
"error",
"${tag}发生错误: ${e.message} ${e.stackTraceToString()}"
)
}
fun getIpAddressInLocalNetwork(): String {
val networkInterfaces = NetworkInterface.getNetworkInterfaces().iterator().asSequence()
val localAddresses = networkInterfaces.flatMap {
it.inetAddresses.asSequence()
.filter { inetAddress ->
inetAddress.isSiteLocalAddress && inetAddress?.hostAddress?.contains(":")
.isFalse() &&
inetAddress.hostAddress != "127.0.0.1"
}
.map { inetAddress -> inetAddress.hostAddress }
}
return localAddresses.firstOrNull() ?: "127.0.0.1"
}
inline fun errorDialog(title: String, block: () -> Unit) {
try {
block()
} catch (e: Exception) {
if (e is CancellationException) {
return
}
appScope.launch(Dispatchers.Main) {
showErrorDialog(e, title)
}
}
}
@OptIn(InternalAPI::class)
fun FormBuilder.add(key: String, value: Any, headers: Headers = Headers.Empty) {
append(key.quote(), value, headers)
}
fun formatTime(date: Date): String {
val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
return formatter.format(date)
}
fun Uri.getFileName(): String {
var fileName = ""
app.contentResolver.query(this, null, null, null, null)?.use {
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
it.moveToFirst()
fileName = it.getString(nameIndex)
}
return fileName
}