Files
OwnDroid/app/src/main/java/com/bintianqi/owndroid/Utils.kt
BinTianqi a30a9abb3c Store App lock password hash in shared prefs
Add workflow release.yml
Fix R8 error
2025-04-13 09:05:05 +08:00

135 lines
4.5 KiB
Kotlin

package com.bintianqi.owndroid
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContract
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.Locale
import java.util.concurrent.TimeUnit
import kotlin.reflect.typeOf
var zhCN = true
fun uriToStream(
context: Context,
uri: Uri,
operation: (stream: InputStream)->Unit
){
try {
context.contentResolver.openInputStream(uri)?.use {
operation(it)
}
}
catch(_: FileNotFoundException) { Toast.makeText(context, R.string.file_not_exist, Toast.LENGTH_SHORT).show() }
catch(_: IOException) { Toast.makeText(context, R.string.io_exception, Toast.LENGTH_SHORT).show() }
}
fun writeClipBoard(context: Context, string: String):Boolean{
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
try {
clipboardManager.setPrimaryClip(ClipData.newPlainText("", string))
} catch(_:Exception) {
return false
}
return true
}
fun formatFileSize(bytes: Long): String {
val kb = 1024
val mb = kb * 1024
val gb = mb * 1024
return when {
bytes >= gb -> String.format(Locale.US, "%.2f GB", bytes / gb.toDouble())
bytes >= mb -> String.format(Locale.US, "%.2f MB", bytes / mb.toDouble())
bytes >= kb -> String.format(Locale.US, "%.2f KB", bytes / kb.toDouble())
else -> "$bytes bytes"
}
}
val Boolean.yesOrNo
@StringRes get() = if(this) R.string.yes else R.string.no
@RequiresApi(26)
fun parseTimestamp(timestamp: Long): String {
val instant = Instant.ofEpochMilli(timestamp)
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault())
return formatter.format(instant)
}
fun parseDate(date: Date): String = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(date)
val Long.humanReadableDate: String
get() = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(Date(this))
fun formatDate(pattern: String, value: Long): String
= SimpleDateFormat(pattern, Locale.getDefault()).format(Date(value))
fun Context.showOperationResultToast(success: Boolean) {
Toast.makeText(this, if(success) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
}
const val APK_MIME = "application/vnd.android.package-archive"
inline fun <reified T> serializableNavTypePair() =
typeOf<T>() to object : NavType<T>(false) {
override fun get(bundle: Bundle, key: String): T? =
bundle.getString(key)?.let { parseValue(it) }
override fun put(bundle: Bundle, key: String, value: T) =
bundle.putString(key, serializeAsValue(value))
override fun parseValue(value: String): T =
Json.decodeFromString(value)
override fun serializeAsValue(value: T): String =
Json.encodeToString(value)
}
class ChoosePackageContract: ActivityResultContract<Nothing?, String?>() {
override fun createIntent(context: Context, input: Nothing?): Intent =
Intent(context, PackageChooserActivity::class.java)
override fun parseResult(resultCode: Int, intent: Intent?): String? =
intent?.getStringExtra("package")
}
fun exportLogs(context: Context, uri: Uri) {
context.contentResolver.openOutputStream(uri)?.use { output ->
val proc = Runtime.getRuntime().exec("logcat -d")
proc.inputStream.copyTo(output)
if(Build.VERSION.SDK_INT >= 26) proc.waitFor(2L, TimeUnit.SECONDS)
else proc.waitFor()
context.showOperationResultToast(proc.exitValue() == 0)
}
}
fun <T> NavHostController.navigate(route: T, args: Bundle) {
navigate(graph.findNode(route)!!.id, args)
}
val HorizontalPadding = 16.dp
@OptIn(ExperimentalStdlibApi::class)
fun String.hash(): String {
val md = MessageDigest.getInstance("SHA-256")
return md.digest(this.encodeToByteArray()).toHexString()
}