feat: offline package download/import, load progress, local-first open

This commit is contained in:
2026-08-17 13:11:32 +08:00
parent 010076ff28
commit 49b84f83ad
6 changed files with 347 additions and 10 deletions
@@ -19,6 +19,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowInsetsController
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.FrameLayout
@@ -36,6 +37,7 @@ import top.yeij.hearth.webapp.OkHttpHttpClient
import top.yeij.hearth.webapp.Tab
import top.yeij.hearth.webapp.WebAppContainer
import top.yeij.hearth.webapp.WebAppRepository
import top.yeij.hearth.webapp.WebAppStorage
import top.yeij.hearth.webview.BrightnessProvider
import top.yeij.hearth.webview.JsBridge
import top.yeij.hearth.webview.NotificationAccessProvider
@@ -67,9 +69,15 @@ class MainActivity : Activity() {
// Global sidebar collapsed width (dp); content WebViews reserve it on the
// left to avoid covering the sidebar
private const val SIDEBAR_WIDTH_DP = 80
// SAF 导入离线包的 requestCode
private const val REQUEST_IMPORT_ZIP = 1001
}
private val webAppContainer = WebAppContainer()
private val webAppStorage = WebAppStorage(this)
// 待导入离线包的 webapp idSAF 回调用)
// Pending webapp id for offline-package import (used in the SAF callback)
private var pendingImportId: String? = null
private val gson = Gson()
private val webAppHost = WebAppHostImpl()
@@ -205,6 +213,8 @@ class MainActivity : Activity() {
serverUrl = serverUrlProvider,
wallpaper = wallpaperProvider,
onShowTabPanel = { showNativeTabPanel() },
storage = webAppStorage,
onImportOfflinePackage = { id -> startImportOfflinePackage(id) },
)
root = FrameLayout(this)
setContentView(root)
@@ -273,6 +283,16 @@ class MainActivity : Activity() {
view?.loadDataWithBaseURL(null, errorPage(description ?: "未知错误"), "text/html", "utf-8", null)
}
}
webView.webChromeClient = object : WebChromeClient() {
override fun onProgressChanged(view: WebView?, newProgress: Int) {
// 推送 webapp 页面加载进度给 H50-100
// Push the webapp page load progress to H5 (0-100)
desktopWebView.evaluateJavascript(
"window.HearthEvents && window.HearthEvents.webappProgress('$id', $newProgress);",
null
)
}
}
val params = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
@@ -416,6 +436,35 @@ class MainActivity : Activity() {
// dp to px
private fun dp(v: Int): Int = (v * resources.displayMetrics.density).toInt()
// 触发 SAF 选择 zip 导入离线包
// Trigger SAF to pick a zip for offline-package import
private fun startImportOfflinePackage(id: String) {
pendingImportId = id
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/zip"
}
startActivityForResult(intent, REQUEST_IMPORT_ZIP)
}
// SAF 结果:导入离线包,成功后通知 H5(下次点开用本地)
// SAF result: import the offline package, notify H5 on success (next open uses local)
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_IMPORT_ZIP && resultCode == Activity.RESULT_OK) {
val id = pendingImportId ?: return
val uri = data?.data ?: return
val ok = webAppStorage.importPackage(id, uri)
Log.d(TAG, "importPackage: id=$id ok=$ok")
desktopWebView.evaluateJavascript(
"window.HearthEvents && window.HearthEvents.webappImported('$id', $ok);",
null
)
}
pendingImportId = null
}
// 检测通知使用权是否已授予,未授予时打日志提示(UI 引导入口留后续)
// Check whether notification access is granted; log a hint when not
// (the settings UI entry is deferred to a later task)