package com.follow.clash import android.app.Application import android.content.Context.MODE_PRIVATE import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.os.Build import android.os.Handler import android.os.Looper import android.widget.Toast import androidx.core.graphics.drawable.toBitmap import com.follow.clash.common.GlobalState import com.follow.clash.models.SharedState import com.google.gson.Gson import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodChannel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import java.io.File import java.io.FileOutputStream import java.util.concurrent.TimeUnit import kotlin.coroutines.resume private const val ICON_TTL_DAYS = 1L val Application.sharedState: SharedState get() { try { val sp = getSharedPreferences("FlutterSharedPreferences", MODE_PRIVATE) val res = sp.getString("flutter.sharedState", "") return Gson().fromJson(res, SharedState::class.java) } catch (_: Exception) { return SharedState() } } private var lastToast: Toast? = null fun Application.showToast(text: String?) { Handler(Looper.getMainLooper()).post { lastToast?.cancel() lastToast = Toast.makeText(this, text, Toast.LENGTH_LONG).apply { show() } } } suspend fun PackageManager.getPackageIconPath(packageName: String): String = withContext(Dispatchers.IO) { val cacheDir = GlobalState.application.cacheDir val iconDir = File(cacheDir, "icons").apply { mkdirs() } return@withContext try { val pkgInfo = getPackageInfo(packageName, 0) val lastUpdateTime = pkgInfo.lastUpdateTime val iconFile = File(iconDir, "${packageName}_${lastUpdateTime}.webp") if (iconFile.exists() && !isExpired(iconFile)) { return@withContext iconFile.absolutePath } iconDir.listFiles { f -> f.name.startsWith("${packageName}_") }?.forEach(File::delete) val icon = getApplicationIcon(packageName) saveDrawableToFile(icon, iconFile) iconFile.absolutePath } catch (_: Exception) { val defaultIconFile = File(iconDir, "default_icon.webp") if (!defaultIconFile.exists()) { saveDrawableToFile(defaultActivityIcon, defaultIconFile) } defaultIconFile.absolutePath } } private suspend fun saveDrawableToFile(drawable: Drawable, file: File) { val bitmap = withContext(Dispatchers.Default) { drawable.toBitmap(width = 128, height = 128) } try { val format = when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { Bitmap.CompressFormat.WEBP_LOSSY } else -> { Bitmap.CompressFormat.WEBP } } FileOutputStream(file).use { fos -> bitmap.compress(format, 90, fos) } } finally { if (!bitmap.isRecycled) bitmap.recycle() } } private fun isExpired(file: File): Boolean { val now = System.currentTimeMillis() val age = now - file.lastModified() return age > TimeUnit.DAYS.toMillis(ICON_TTL_DAYS) } suspend fun MethodChannel.awaitResult( method: String, arguments: Any? = null ): T? = withContext(Dispatchers.Main) { suspendCancellableCoroutine { continuation -> invokeMethod(method, arguments, object : MethodChannel.Result { override fun success(result: Any?) { @Suppress("UNCHECKED_CAST") continuation.resume(result as T?) } override fun error(code: String, message: String?, details: Any?) { continuation.resume(null) } override fun notImplemented() { continuation.resume(null) } }) } } inline fun FlutterEngine.plugin(): T? { return plugins.get(T::class.java) as T? } fun MethodChannel.invokeMethodOnMainThread( method: String, arguments: Any? = null, callback: ((Result) -> Unit)? = null ) { Handler(Looper.getMainLooper()).post { invokeMethod(method, arguments, object : MethodChannel.Result { override fun success(result: Any?) { @Suppress("UNCHECKED_CAST") callback?.invoke(Result.success(result as T)) } override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { val exception = Exception("MethodChannel error: $errorCode - $errorMessage") callback?.invoke(Result.failure(exception)) } override fun notImplemented() { val exception = NotImplementedError("Method not implemented: $method") callback?.invoke(Result.failure(exception)) } }) } }