feat: h5 webapp list page with rich cards
This commit is contained in:
@@ -99,3 +99,34 @@ body {
|
|||||||
|
|
||||||
.cell .ic { width: 48px; height: 48px; border-radius: 12px; }
|
.cell .ic { width: 48px; height: 48px; border-radius: 12px; }
|
||||||
.cell .lbl { font-size: 12px; color: var(--text-dim); text-align: center; }
|
.cell .lbl { font-size: 12px; color: var(--text-dim); text-align: center; }
|
||||||
|
|
||||||
|
/* H5 应用列表页:富卡片 + 在线/离线标签 */
|
||||||
|
/* H5 web app list page: rich cards + online/offline badge */
|
||||||
|
.wcell {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
background: var(--glass-bg);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 12px 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wcell .ic { width: 48px; height: 48px; border-radius: 12px; }
|
||||||
|
.wcell .lbl { font-size: 12px; color: var(--text-dim); text-align: center; }
|
||||||
|
|
||||||
|
.wcell .tag {
|
||||||
|
position: absolute;
|
||||||
|
top: 6px;
|
||||||
|
right: 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--glass-bg);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
<script src="js/bridge.js"></script>
|
<script src="js/bridge.js"></script>
|
||||||
<script src="js/router.js"></script>
|
<script src="js/router.js"></script>
|
||||||
<script src="js/pages/applist.js"></script>
|
<script src="js/pages/applist.js"></script>
|
||||||
|
<script src="js/pages/webapplist.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// 渲染侧边栏导航项 / render sidebar nav items
|
// 渲染侧边栏导航项 / render sidebar nav items
|
||||||
const nav = document.getElementById('rail-nav');
|
const nav = document.getElementById('rail-nav');
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// H5 应用列表页:富卡片 + 在线/离线标签 + 搜索
|
||||||
|
// H5 web app list page: rich cards + online/offline badge + search
|
||||||
|
window.loadWebAppList = async function () {
|
||||||
|
const page = document.querySelector('[data-page="webapplist"]');
|
||||||
|
if (page.dataset.loaded) return; page.dataset.loaded = '1';
|
||||||
|
page.innerHTML = `
|
||||||
|
<div class="search"><input id="web-search" placeholder="搜索 webAPP…"></div>
|
||||||
|
<div class="grid" id="web-grid"></div>`;
|
||||||
|
const apps = JSON.parse(await bridge.call('fetchWebApps'));
|
||||||
|
const grid = document.getElementById('web-grid');
|
||||||
|
const render = (list) => {
|
||||||
|
grid.innerHTML = list.map(a => `
|
||||||
|
<button class="wcell" data-id="${a.id}" data-url="${a.url}">
|
||||||
|
<img class="ic" src="${a.icon}" alt="">
|
||||||
|
<span class="lbl">${a.name}</span>
|
||||||
|
<span class="tag">${a.offline ? '离线' : '在线'}</span>
|
||||||
|
</button>`).join('');
|
||||||
|
grid.querySelectorAll('.wcell').forEach(el => el.onclick = () => bridge.call('openWebApp', el.dataset.id));
|
||||||
|
};
|
||||||
|
render(apps);
|
||||||
|
document.getElementById('web-search').oninput = (e) => {
|
||||||
|
const kw = e.target.value.toLowerCase();
|
||||||
|
render(apps.filter(a => a.name.toLowerCase().includes(kw)));
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -4,8 +4,16 @@ import com.google.gson.Gson
|
|||||||
import top.yeij.hearth.cache.CacheManager
|
import top.yeij.hearth.cache.CacheManager
|
||||||
|
|
||||||
// Web 应用清单条目
|
// Web 应用清单条目
|
||||||
|
// offline 为离线包路径,null 表示纯在线应用
|
||||||
// Web app manifest entry
|
// Web app manifest entry
|
||||||
data class WebApp(val id: String, val name: String, val icon: String, val url: String)
|
// offline is the offline package path, null means online-only
|
||||||
|
data class WebApp(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val icon: String,
|
||||||
|
val url: String,
|
||||||
|
val offline: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
// 网络抽象接口:Task 10 的 CardRepository 也会复用
|
// 网络抽象接口:Task 10 的 CardRepository 也会复用
|
||||||
// HTTP abstraction reused by Task 10 CardRepository
|
// HTTP abstraction reused by Task 10 CardRepository
|
||||||
@@ -41,7 +49,10 @@ class WebAppRepository(
|
|||||||
val name = map["name"] as? String ?: return@mapNotNull null
|
val name = map["name"] as? String ?: return@mapNotNull null
|
||||||
val icon = map["icon"] as? String ?: return@mapNotNull null
|
val icon = map["icon"] as? String ?: return@mapNotNull null
|
||||||
val url = map["url"] as? String ?: return@mapNotNull null
|
val url = map["url"] as? String ?: return@mapNotNull null
|
||||||
WebApp(id, name, icon, url)
|
// 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package top.yeij.hearth.webview
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import top.yeij.hearth.app.AppRepository
|
import top.yeij.hearth.app.AppRepository
|
||||||
|
import top.yeij.hearth.webapp.WebApp
|
||||||
|
import top.yeij.hearth.webapp.WebAppRepository
|
||||||
|
|
||||||
class JsBridge(
|
class JsBridge(
|
||||||
private val deviceWidthPx: Int,
|
private val deviceWidthPx: Int,
|
||||||
@@ -9,6 +11,7 @@ class JsBridge(
|
|||||||
private val density: Float,
|
private val density: Float,
|
||||||
private val darkMode: Boolean,
|
private val darkMode: Boolean,
|
||||||
private val appRepository: AppRepository? = null,
|
private val appRepository: AppRepository? = null,
|
||||||
|
private val webAppRepository: WebAppRepository? = null,
|
||||||
) {
|
) {
|
||||||
private val gson = com.google.gson.Gson()
|
private val gson = com.google.gson.Gson()
|
||||||
|
|
||||||
@@ -34,4 +37,9 @@ class JsBridge(
|
|||||||
// Launch an app by package name, true on success (false when no repository wired)
|
// Launch an app by package name, true on success (false when no repository wired)
|
||||||
@android.webkit.JavascriptInterface
|
@android.webkit.JavascriptInterface
|
||||||
fun launchApp(packageName: String): Boolean = appRepository?.launchApp(packageName) ?: false
|
fun launchApp(packageName: String): Boolean = appRepository?.launchApp(packageName) ?: false
|
||||||
|
|
||||||
|
// 返回 H5 应用清单 JSON(无仓库时返回空数组)
|
||||||
|
// Return the H5 web app manifest JSON (empty array when no repository wired)
|
||||||
|
@android.webkit.JavascriptInterface
|
||||||
|
fun fetchWebApps(): String = gson.toJson(webAppRepository?.fetchManifest() ?: emptyList<WebApp>())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user