feat: offline package version management, long-press menu
This commit is contained in:
@@ -14,6 +14,7 @@ data class WebApp(
|
||||
val icon: String,
|
||||
val url: String,
|
||||
val offline: String? = null,
|
||||
val offlineVersion: String? = null,
|
||||
)
|
||||
|
||||
// 网络抽象接口:Task 10 的 CardRepository 也会复用
|
||||
@@ -93,10 +94,12 @@ class WebAppRepository(
|
||||
val name = map["name"] as? String ?: return@mapNotNull null
|
||||
val icon = map["icon"] as? String ?: return@mapNotNull null
|
||||
val url = map["url"] as? String ?: return@mapNotNull null
|
||||
// offline 是对象时取其 package 字段,否则 null(纯在线)
|
||||
// offline is an object -> take its package field, otherwise null (online-only)
|
||||
val offline = (map["offline"] as? Map<*, *>)?.get("package") as? String
|
||||
WebApp(id, name, icon, url, offline)
|
||||
// offline 是对象时取其 package/version 字段,否则 null(纯在线)
|
||||
// offline is an object -> take its package/version fields, otherwise null (online-only)
|
||||
val offlineMap = map["offline"] as? Map<*, *>
|
||||
val offline = offlineMap?.get("package") as? String
|
||||
val offlineVersion = offlineMap?.get("version") as? String
|
||||
WebApp(id, name, icon, url, offline, offlineVersion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,23 @@ class WebAppStorage(private val context: Context) {
|
||||
fun localIndexUrl(id: String): String? =
|
||||
if (hasLocalPackage(id)) "file://" + File(appDir(id), "index.html").absolutePath else null
|
||||
|
||||
// 下载并解压离线包,onProgress 回调 0-100
|
||||
// Download and extract the offline package, onProgress reports 0-100
|
||||
fun downloadAndExtract(id: String, packageUrl: String, onProgress: (Int) -> Unit): Boolean {
|
||||
// 本地离线包版本号(解压目录里的 .version 文件),无则 null
|
||||
// Local offline-package version (from the .version file), null when absent
|
||||
fun getLocalVersion(id: String): String? {
|
||||
val f = File(appDir(id), ".version")
|
||||
return if (f.exists()) f.readText().trim().takeIf { it.isNotEmpty() } else null
|
||||
}
|
||||
|
||||
// 删除本地离线包
|
||||
// Delete the local offline package
|
||||
fun deleteLocalPackage(id: String) {
|
||||
appDir(id).deleteRecursively()
|
||||
Log.d(TAG, "deleteLocalPackage: id=$id")
|
||||
}
|
||||
|
||||
// 下载并解压离线包(记录版本号),onProgress 回调 0-100
|
||||
// Download and extract the offline package (record its version), onProgress 0-100
|
||||
fun downloadAndExtract(id: String, packageUrl: String, version: String, onProgress: (Int) -> Unit): Boolean {
|
||||
val tmp = File(context.cacheDir, "$id-offline.zip")
|
||||
return try {
|
||||
val request = Request.Builder().url(packageUrl).build()
|
||||
@@ -57,6 +71,7 @@ class WebAppStorage(private val context: Context) {
|
||||
}
|
||||
}
|
||||
val ok = extractZip(tmp, appDir(id))
|
||||
if (ok) writeVersion(id, version)
|
||||
tmp.delete()
|
||||
ok
|
||||
} catch (e: Exception) {
|
||||
@@ -66,14 +81,15 @@ class WebAppStorage(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 手动导入离线包:从 uri 复制 zip 并解压
|
||||
// Manually import an offline package: copy the zip from uri and extract
|
||||
// 手动导入离线包:从 uri 复制 zip 并解压(版本记为 manual)
|
||||
// Manually import an offline package: copy the zip from uri and extract (version "manual")
|
||||
fun importPackage(id: String, uri: Uri): Boolean {
|
||||
val tmp = File(context.cacheDir, "$id-import.zip")
|
||||
return try {
|
||||
val input = context.contentResolver.openInputStream(uri) ?: return false
|
||||
FileOutputStream(tmp).use { output -> input.use { it.copyTo(output) } }
|
||||
val ok = extractZip(tmp, appDir(id))
|
||||
if (ok) writeVersion(id, "manual")
|
||||
tmp.delete()
|
||||
ok
|
||||
} catch (e: Exception) {
|
||||
@@ -83,6 +99,16 @@ class WebAppStorage(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 记录本地离线包版本号(.version 文件)
|
||||
// Record the local offline-package version (a .version file)
|
||||
private fun writeVersion(id: String, version: String) {
|
||||
try {
|
||||
File(appDir(id), ".version").writeText(version)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "writeVersion failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
// 解压 zip 到目标目录(先清空旧目录),校验 index.html 存在
|
||||
// Extract the zip into the destination (clear it first), verify index.html exists
|
||||
private fun extractZip(zip: File, dest: File): Boolean {
|
||||
|
||||
@@ -150,7 +150,7 @@ class JsBridge(
|
||||
pushWebappProgress(id, 0)
|
||||
Thread {
|
||||
val fullUrl = resolveUrl(pkg)
|
||||
st.downloadAndExtract(id, fullUrl) { p -> pushWebappProgress(id, p) }
|
||||
st.downloadAndExtract(id, fullUrl, app.offlineVersion ?: "") { p -> pushWebappProgress(id, p) }
|
||||
val url = st.localIndexUrl(id) ?: app.url
|
||||
openWebAppAt(container, host, id, url, app.name)
|
||||
pushWebappProgress(id, 100)
|
||||
@@ -185,6 +185,36 @@ class JsBridge(
|
||||
@android.webkit.JavascriptInterface
|
||||
fun hasLocalPackage(id: String): Boolean = storage?.hasLocalPackage(id) ?: false
|
||||
|
||||
// 本地离线包版本号(无则空字符串)
|
||||
// Local offline-package version (empty string when absent)
|
||||
@android.webkit.JavascriptInterface
|
||||
fun getLocalVersion(id: String): String = storage?.getLocalVersion(id) ?: ""
|
||||
|
||||
// 删除本地离线包
|
||||
// Delete the local offline package
|
||||
@android.webkit.JavascriptInterface
|
||||
fun deleteLocalPackage(id: String) {
|
||||
Log.d("HearthBridge", "deleteLocalPackage: id=$id")
|
||||
storage?.deleteLocalPackage(id)
|
||||
}
|
||||
|
||||
// 强制更新离线包:删除本地后重新下载
|
||||
// Force-update the offline package: delete local then re-download
|
||||
@android.webkit.JavascriptInterface
|
||||
fun updateOfflinePackage(id: String) {
|
||||
Log.d("HearthBridge", "updateOfflinePackage: id=$id")
|
||||
val st = storage ?: return
|
||||
val app = manifest().firstOrNull { it.id == id } ?: return
|
||||
val pkg = app.offline ?: return
|
||||
st.deleteLocalPackage(id)
|
||||
pushWebappProgress(id, 0)
|
||||
Thread {
|
||||
val fullUrl = resolveUrl(pkg)
|
||||
st.downloadAndExtract(id, fullUrl, app.offlineVersion ?: "") { p -> pushWebappProgress(id, p) }
|
||||
pushWebappProgress(id, 100)
|
||||
}.start()
|
||||
}
|
||||
|
||||
// 推送 webapp 加载/下载进度给 H5(0-100)
|
||||
// Push webapp load/download progress to H5 (0-100)
|
||||
private fun pushWebappProgress(id: String, progress: Int) {
|
||||
|
||||
Reference in New Issue
Block a user