package com.catlas.escapepenacony.data import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build import android.os.Environment import android.provider.Settings import androidx.core.content.FileProvider import com.catlas.escapepenacony.BuildConfig import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONObject import java.io.File import java.io.FileOutputStream import java.net.HttpURLConnection import java.net.URL data class ReleaseInfo( val tagName: String, val releaseTitle: String, val changelog: String, val apkDownloadUrl: String, val isNewer: Boolean ) object UpdateManager { private const val GITHUB_RELEASES_API = "https://api.github.com/repos/CAtlas0784/Escape-penacony/releases/latest" private const val GITHUB_RAW_SCENES_URL = "https://raw.githubusercontent.com/CAtlas0784/Escape-penacony/main/scenes.json" suspend fun checkForAppUpdate(currentVersion: String = BuildConfig.VERSION_NAME): Result = withContext(Dispatchers.IO) { try { val url = URL(GITHUB_RELEASES_API) val conn = url.openConnection() as HttpURLConnection conn.connectTimeout = 8000 conn.readTimeout = 8000 conn.setRequestProperty("Accept", "application/vnd.github.v3+json") conn.setRequestProperty("User-Agent", "Escape-Penacony-Android-App") if (conn.responseCode == 200) { val body = conn.inputStream.bufferedReader().use { it.readText() } val json = JSONObject(body) val tagName = json.getString("tag_name") val title = json.optString("name", tagName) val changelog = json.optString("body", "No changelog provided.") var downloadUrl = "" val assets = json.optJSONArray("assets") if (assets != null) { for (i in 0 until assets.length()) { val asset = assets.getJSONObject(i) val name = asset.optString("name", "") if (name.endsWith(".apk", ignoreCase = true)) { downloadUrl = asset.getString("browser_download_url") break } } } val isNewer = isNewerVersion(currentVersion, tagName) Result.success( ReleaseInfo( tagName = tagName, releaseTitle = title, changelog = changelog, apkDownloadUrl = downloadUrl, isNewer = isNewer ) ) } else { Result.success(null) } } catch (e: Exception) { e.printStackTrace() Result.failure(e) } } suspend fun downloadApk( context: Context, downloadUrl: String, onProgress: (Int) -> Unit ): Result = withContext(Dispatchers.IO) { try { val url = URL(downloadUrl) val conn = url.openConnection() as HttpURLConnection conn.connectTimeout = 15000 conn.readTimeout = 15000 conn.setRequestProperty("User-Agent", "Escape-Penacony-Android-App") val fileLength = conn.contentLength val dir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) ?: context.filesDir val apkFile = File(dir, "Escape-penacony-update.apk") conn.inputStream.use { input -> FileOutputStream(apkFile).use { output -> val buffer = ByteArray(8 * 1024) var total: Long = 0 var count: Int while (input.read(buffer).also { count = it } != -1) { total += count if (fileLength > 0) { val progress = ((total * 100) / fileLength).toInt() withContext(Dispatchers.Main) { onProgress(progress) } } output.write(buffer, 0, count) } } } Result.success(apkFile) } catch (e: Exception) { e.printStackTrace() Result.failure(e) } } fun promptInstallApk(context: Context, apkFile: File) { // Android 8.0+ Check Unknown App Sources permission if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (!context.packageManager.canRequestPackageInstalls()) { val settingsIntent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply { data = Uri.parse("package:${context.packageName}") addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(settingsIntent) return } } val apkUri: Uri = FileProvider.getUriForFile( context, "${context.packageName}.fileprovider", apkFile ) val installIntent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(apkUri, "application/vnd.android.package-archive") addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(installIntent) } suspend fun fetchLatestScenesJson(): Result = withContext(Dispatchers.IO) { try { val url = URL(GITHUB_RAW_SCENES_URL) val conn = url.openConnection() as HttpURLConnection conn.connectTimeout = 8000 conn.readTimeout = 8000 conn.setRequestProperty("User-Agent", "Escape-Penacony-Android-App") if (conn.responseCode == 200) { val content = conn.inputStream.bufferedReader().use { it.readText() } Result.success(content) } else { Result.failure(Exception("HTTP error ${conn.responseCode}")) } } catch (e: Exception) { Result.failure(e) } } private fun isNewerVersion(current: String, latest: String): Boolean { val c = current.removePrefix("v").split(".").map { it.toIntOrNull() ?: 0 } val l = latest.removePrefix("v").split(".").map { it.toIntOrNull() ?: 0 } val maxLen = maxOf(c.size, l.size) for (i in 0 until maxLen) { val cv = c.getOrElse(i) { 0 } val lv = l.getOrElse(i) { 0 } if (lv > cv) return true if (lv < cv) return false } return false } }