feat: per-app initial scale config

This commit is contained in:
2026-08-17 20:01:01 +08:00
parent b65a0400a6
commit bb714d8de3
5 changed files with 23 additions and 12 deletions
@@ -271,12 +271,12 @@ class MainActivity : Activity() {
// Failed webapp ids: on Back, close directly instead of looping goBack // Failed webapp ids: on Back, close directly instead of looping goBack
private val failedWebViews = mutableSetOf<String>() private val failedWebViews = mutableSetOf<String>()
override fun openWebView(id: String, url: String, ua: String?) { override fun openWebView(id: String, url: String, ua: String?, scale: Int?) {
if (contentWebViews.containsKey(id)) { if (contentWebViews.containsKey(id)) {
switchWebView(id) switchWebView(id)
return return
} }
val webView = WebViewManager(this@MainActivity).createContentWebView(ua) val webView = WebViewManager(this@MainActivity).createContentWebView(ua, scale)
webView.webViewClient = object : WebViewClient() { webView.webViewClient = object : WebViewClient() {
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION") @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
override fun onReceivedError( override fun onReceivedError(
@@ -17,6 +17,7 @@ data class WebApp(
val offline: String? = null, val offline: String? = null,
val offlineVersion: String? = null, val offlineVersion: String? = null,
val ua: String? = null, val ua: String? = null,
val scale: Int? = null,
) )
// 网络抽象接口:Task 10 的 CardRepository 也会复用 // 网络抽象接口:Task 10 的 CardRepository 也会复用
@@ -111,6 +112,9 @@ class WebAppRepository(
// ua optional: desktop/pc uses a desktop UA, others are custom UA strings, // ua optional: desktop/pc uses a desktop UA, others are custom UA strings,
// absent means the default tablet UA // absent means the default tablet UA
val ua = map["ua"] as? String val ua = map["ua"] as? String
// scale 可选:初始缩放百分比(1-100),用于 PC 版页面缩放适配横屏
// scale optional: initial zoom percentage (1-100), for scaling desktop pages
val scale = (map["scale"] as? Number)?.toInt()
WebApp( WebApp(
id, name, id, name,
resolvePath(icon), resolvePath(icon),
@@ -118,6 +122,7 @@ class WebAppRepository(
offline?.let { resolvePath(it) }, offline?.let { resolvePath(it) },
offlineVersion, offlineVersion,
ua, ua,
scale,
) )
} }
} }
@@ -144,7 +144,7 @@ class JsBridge(
// 1. 本地离线包优先(服务端删除后仍可用) // 1. 本地离线包优先(服务端删除后仍可用)
val localUrl = st?.localIndexUrl(id) val localUrl = st?.localIndexUrl(id)
if (localUrl != null) { if (localUrl != null) {
openWebAppAt(container, host, id, localUrl, app.name, app.ua) openWebAppAt(container, host, id, localUrl, app.name, app.ua, app.scale)
return return
} }
// 2. 有离线包 → 后台下载解压(推送进度),完成后加载本地;失败回退远程 // 2. 有离线包 → 后台下载解压(推送进度),完成后加载本地;失败回退远程
@@ -155,24 +155,24 @@ class JsBridge(
val fullUrl = resolveUrl(pkg) val fullUrl = resolveUrl(pkg)
st.downloadAndExtract(id, fullUrl, app.offlineVersion ?: "") { p -> pushWebappProgress(id, p) } st.downloadAndExtract(id, fullUrl, app.offlineVersion ?: "") { p -> pushWebappProgress(id, p) }
val url = st.localIndexUrl(id) ?: app.url val url = st.localIndexUrl(id) ?: app.url
openWebAppAt(container, host, id, url, app.name, app.ua) openWebAppAt(container, host, id, url, app.name, app.ua, app.scale)
pushWebappProgress(id, 100) pushWebappProgress(id, 100)
}.start() }.start()
return return
} }
// 3. 无离线包 → 远程加载(WebView 自带加载进度) // 3. 无离线包 → 远程加载(WebView 自带加载进度)
openWebAppAt(container, host, id, app.url, app.name, app.ua) openWebAppAt(container, host, id, app.url, app.name, app.ua, app.scale)
} }
// 在主线程打开指定 url 的 webAPP 标签 // 在主线程打开指定 url 的 webAPP 标签
// Open a webapp tab at the given url on the main thread // Open a webapp tab at the given url on the main thread
private fun openWebAppAt(container: WebAppContainer, host: WebAppHost, id: String, url: String, name: String, ua: String?) { private fun openWebAppAt(container: WebAppContainer, host: WebAppHost, id: String, url: String, name: String, ua: String?, scale: Int?) {
onMain { onMain {
container.open(id, url, name) container.open(id, url, name)
host.openWebView(id, url, ua) host.openWebView(id, url, ua, scale)
host.syncTabs(container.tabs()) host.syncTabs(container.tabs())
} }
Log.d("HearthBridge", "openWebApp: id=$id url=$url ua=$ua") Log.d("HearthBridge", "openWebApp: id=$id url=$url ua=$ua scale=$scale")
} }
// 手动导入离线包(由 MainActivity 触发 SAF 选择 zip // 手动导入离线包(由 MainActivity 触发 SAF 选择 zip
@@ -8,10 +8,11 @@ import top.yeij.hearth.webapp.Tab
// multi-WebView lifecycle, visibility, and navigation, and syncing tab state // multi-WebView lifecycle, visibility, and navigation, and syncing tab state
// back to the desktop H5 topbar // back to the desktop H5 topbar
interface WebAppHost { interface WebAppHost {
// 创建内容 WebView 加载 url 并显示(已存在则切换到该标签);ua 为清单声明的 UA // 创建内容 WebView 加载 url 并显示(已存在则切换到该标签);ua 为清单声明的 UA
// scale 为清单声明的初始缩放百分比(可为 null)
// Create a content WebView loading url and show it (switch if already open); // Create a content WebView loading url and show it (switch if already open);
// ua is the UA declared in the manifest // ua is the manifest-declared UA, scale is the initial zoom percentage (nullable)
fun openWebView(id: String, url: String, ua: String?) fun openWebView(id: String, url: String, ua: String?, scale: Int?)
// 销毁对应内容 WebView // 销毁对应内容 WebView
// Destroy the corresponding content WebView // Destroy the corresponding content WebView
@@ -31,7 +31,7 @@ class WebViewManager(private val activity: Activity) {
// (lifecycle is managed by the host); transparent background so a webapp without // (lifecycle is managed by the host); transparent background so a webapp without
// a declared background shows the wallpaper instead of a default white // a declared background shows the wallpaper instead of a default white
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
fun createContentWebView(ua: String?): WebView { fun createContentWebView(ua: String?, scale: Int?): WebView {
val webView = WebView(activity) val webView = WebView(activity)
webView.setBackgroundColor(Color.TRANSPARENT) webView.setBackgroundColor(Color.TRANSPARENT)
configure(webView) configure(webView)
@@ -43,6 +43,11 @@ class WebViewManager(private val activity: Activity) {
ua == "desktop" || ua == "pc" -> UA_DESKTOP ua == "desktop" || ua == "pc" -> UA_DESKTOP
else -> ua else -> ua
} }
// 初始缩放(1-100):用于 PC 版页面缩放适配横屏
// Initial zoom (1-100): scale desktop pages to fit landscape
if (scale != null && scale in 1..100) {
webView.setInitialScale(scale)
}
return webView return webView
} }