feat: bundle demo webapp in apk

This commit is contained in:
2026-08-16 20:00:26 +08:00
parent 5f5d0431ee
commit c82418f846
2 changed files with 85 additions and 5 deletions
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>示例应用</title>
<style>
/* Miuix 风格示例页面 */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, "MiSans", "PingFang SC", sans-serif;
background: #f5f5f7;
color: #1a1a1a;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 24px;
}
.icon {
width: 72px; height: 72px; border-radius: 20px;
background: linear-gradient(135deg, #ff6900, #ff9d00);
display: flex; align-items: center; justify-content: center;
font-size: 34px; color: #fff;
}
h1 { font-size: 24px; font-weight: 600; }
.card {
background: #fff; border-radius: 16px; padding: 16px 20px;
width: 100%; max-width: 360px;
box-shadow: 0 2px 12px rgba(0,0,0,.06);
}
.row { display: flex; justify-content: space-between; padding: 8px 0; font-size: 14px; }
.row .k { color: #8a8a90; }
.note { font-size: 12px; color: #8a8a90; text-align: center; line-height: 1.6; }
</style>
</head>
<body>
<div class="icon">🎨</div>
<h1>示例应用</h1>
<div class="card">
<div class="row"><span class="k">来源</span><span>Hearth 内置</span></div>
<div class="row"><span class="k">当前时间</span><span id="clock">--:--:--</span></div>
</div>
<p class="note">这是打包在 Hearth APK 内的示例 webapp<br>用于演示 webAPP 打开 / 顶栏导航 / 多标签</p>
<script>
function tick() {
var now = new Date();
var p = function (n) { return String(n).padStart(2, '0'); };
document.getElementById('clock').textContent =
p(now.getHours()) + ':' + p(now.getMinutes()) + ':' + p(now.getSeconds());
}
tick();
setInterval(tick, 1000);
</script>
</body>
</html>
@@ -44,15 +44,37 @@ class WebAppRepository(
// 先解析、成功才缓存,避免畸形 body 或空结果被永久缓存 // 先解析、成功才缓存,避免畸形 body 或空结果被永久缓存
// Parse first, cache only on success (never cache malformed/empty bodies) // Parse first, cache only on success (never cache malformed/empty bodies)
val apps = parse(body) val apps = parse(body)
if (apps != null) { if (apps != null && apps.isNotEmpty()) {
cache.save(KEY, body) cache.save(KEY, body)
return apps return apps
} }
Log.w(TAG, "fetchManifest: invalid body, not cached") Log.w(TAG, "fetchManifest: invalid/empty body, not cached")
return emptyList() return builtinWebApps()
} }
val cached = cache.load(KEY) ?: return emptyList() val cached = cache.load(KEY)
return parse(cached) ?: emptyList() if (cached != null) {
val apps = parse(cached)
if (apps != null && apps.isNotEmpty()) return apps
}
// 无远程清单:返回内置示例 webapp,保证列表不为空、可演示
// No remote manifest: return the builtin demo webapp so the list is non-empty
return builtinWebApps()
}
// 内置示例 webapp(打包在 assets 内,无需服务器)
// Builtin demo webapp (bundled in assets, no server required)
private fun builtinWebApps(): List<WebApp> {
return listOf(
WebApp(
id = "demo",
name = "示例应用",
icon = "data:image/svg+xml;utf8," +
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'>" +
"<rect width='24' height='24' rx='6' fill='%23ff6900'/>" +
"<text x='12' y='17' font-size='13' text-anchor='middle' fill='white'>H</text></svg>",
url = "file:///android_asset/webapps/demo/index.html",
)
)
} }
// 解析清单 JSON;畸形 JSON 或缺少 apps 字段返回 null(调用方决定回退) // 解析清单 JSON;畸形 JSON 或缺少 apps 字段返回 null(调用方决定回退)