feat: bundle demo webapp in apk
This commit is contained in:
@@ -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 或空结果被永久缓存
|
||||
// Parse first, cache only on success (never cache malformed/empty bodies)
|
||||
val apps = parse(body)
|
||||
if (apps != null) {
|
||||
if (apps != null && apps.isNotEmpty()) {
|
||||
cache.save(KEY, body)
|
||||
return apps
|
||||
}
|
||||
Log.w(TAG, "fetchManifest: invalid body, not cached")
|
||||
return emptyList()
|
||||
Log.w(TAG, "fetchManifest: invalid/empty body, not cached")
|
||||
return builtinWebApps()
|
||||
}
|
||||
val cached = cache.load(KEY) ?: return emptyList()
|
||||
return parse(cached) ?: emptyList()
|
||||
val cached = cache.load(KEY)
|
||||
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(调用方决定回退)
|
||||
|
||||
Reference in New Issue
Block a user