feat: page preload, card grid span, wallpaper into webview for glass blur
This commit is contained in:
@@ -120,25 +120,22 @@ body {
|
|||||||
to { opacity: 1; transform: none; }
|
to { opacity: 1; transform: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 首页三栏布局:三等分纵向列,卡片向下堆叠 */
|
/* 首页卡片 Grid:三等分列,卡片可跨栏(span-2/3),行均分高度 */
|
||||||
/* Home three-column layout: three equal vertical columns, cards stack downward */
|
/* Home card grid: three equal columns, cards can span (span-2/3), rows split evenly */
|
||||||
.tri-col {
|
.tri-col {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-auto-rows: 1fr;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
|
||||||
|
|
||||||
.col {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 16px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card.span-2 { grid-column: span 2; }
|
||||||
|
.card.span-3 { grid-column: span 3; }
|
||||||
|
|
||||||
/* 卡片:柔光玻璃拟态(复用 Task 4 token,非纯色背景) */
|
/* 卡片:柔光玻璃拟态(复用 Task 4 token,非纯色背景) */
|
||||||
/* Card: soft-glass morphism (reuse Task 4 tokens, not a solid color) */
|
/* Card: soft-glass morphism (reuse Task 4 tokens, not a solid color) */
|
||||||
.card {
|
.card {
|
||||||
@@ -161,7 +158,9 @@ body {
|
|||||||
/* 时钟卡缩放权重小:占栏高比例小于其他卡片 */
|
/* 时钟卡缩放权重小:占栏高比例小于其他卡片 */
|
||||||
/* Time card grows less: takes a smaller share of column height than other cards */
|
/* Time card grows less: takes a smaller share of column height than other cards */
|
||||||
.time-card {
|
.time-card {
|
||||||
flex-grow: 0.5;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-card .big {
|
.time-card .big {
|
||||||
@@ -185,6 +184,13 @@ body {
|
|||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 媒体卡:flex column,标题靠上、进度/控制靠下 */
|
||||||
|
/* Media card: flex column, title top, progress/controls bottom */
|
||||||
|
.media-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
/* 媒体卡:正在播放标签 + 标题/艺术家 + 进度条 */
|
/* 媒体卡:正在播放标签 + 标题/艺术家 + 进度条 */
|
||||||
/* Media card: playing caption + title/artist + progress bar */
|
/* Media card: playing caption + title/artist + progress bar */
|
||||||
.media-card .cap {
|
.media-card .cap {
|
||||||
@@ -212,7 +218,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.media-card .prog {
|
.media-card .prog {
|
||||||
margin-top: 12px;
|
margin-top: auto;
|
||||||
|
padding-top: 12px;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: var(--glass-border);
|
background: var(--glass-border);
|
||||||
@@ -225,6 +232,32 @@ body {
|
|||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 媒体控制按钮:上一首/暂停/下一首 */
|
||||||
|
/* Media controls: previous / play-pause / next */
|
||||||
|
.media-card .controls {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-card .controls button {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--glass-border);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-card .controls button:active {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
/* 安卓 APP 列表页:搜索框 + 网格 */
|
/* 安卓 APP 列表页:搜索框 + 网格 */
|
||||||
/* Android app list page: search box + grid */
|
/* Android app list page: search box + grid */
|
||||||
.search { padding: 16px; }
|
.search { padding: 16px; }
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<script src="js/bridge.js"></script>
|
<script src="js/bridge.js"></script>
|
||||||
|
<script src="js/preload.js"></script>
|
||||||
<script src="js/mask.js"></script>
|
<script src="js/mask.js"></script>
|
||||||
<script src="js/cards/cards.js"></script>
|
<script src="js/cards/cards.js"></script>
|
||||||
<script src="js/router.js"></script>
|
<script src="js/router.js"></script>
|
||||||
@@ -55,6 +56,19 @@
|
|||||||
// 初始化背景遮罩(夜间主题 + 已启用时生效)
|
// 初始化背景遮罩(夜间主题 + 已启用时生效)
|
||||||
// Apply wallpaper dim on startup (active under dark theme when enabled)
|
// Apply wallpaper dim on startup (active under dark theme when enabled)
|
||||||
window.mask.apply();
|
window.mask.apply();
|
||||||
|
// 加载壁纸为 body 背景:玻璃效果 backdrop-filter 需要 WebView 内部有背景可模糊
|
||||||
|
// Load the wallpaper as the body background: the glass backdrop-filter needs
|
||||||
|
// in-WebView content behind cards to blur
|
||||||
|
bridge.call('getWallpaper').then(wp => {
|
||||||
|
if (wp) {
|
||||||
|
document.body.style.backgroundImage = 'url(' + wp + ')';
|
||||||
|
document.body.style.backgroundSize = 'cover';
|
||||||
|
document.body.style.backgroundPosition = 'center';
|
||||||
|
}
|
||||||
|
}).catch(() => {});
|
||||||
|
// 首屏渲染后延迟预加载其他页面数据,减少切页等待
|
||||||
|
// Preload other pages' data after first paint to reduce switch latency
|
||||||
|
setTimeout(() => window.preload.loadAll(), 800);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ window.loadAppList = async function () {
|
|||||||
page.innerHTML = `
|
page.innerHTML = `
|
||||||
<div class="search"><input id="app-search" placeholder="搜索应用…"></div>
|
<div class="search"><input id="app-search" placeholder="搜索应用…"></div>
|
||||||
<div class="grid" id="app-grid"></div>`;
|
<div class="grid" id="app-grid"></div>`;
|
||||||
const apps = JSON.parse(await bridge.call('listApps'));
|
// 优先用预加载缓存,未就绪则实时加载
|
||||||
|
// Prefer the preloaded cache; fetch live when not ready yet
|
||||||
|
const apps = window.preload.apps || JSON.parse(await bridge.call('listApps'));
|
||||||
const grid = document.getElementById('app-grid');
|
const grid = document.getElementById('app-grid');
|
||||||
const render = (list) => {
|
const render = (list) => {
|
||||||
grid.textContent = '';
|
grid.textContent = '';
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
// (big time card + generic placeholders for the rest)
|
// (big time card + generic placeholders for the rest)
|
||||||
window.updateHomeCards = async function () {
|
window.updateHomeCards = async function () {
|
||||||
const page = document.querySelector('[data-page="home"]');
|
const page = document.querySelector('[data-page="home"]');
|
||||||
page.innerHTML = `<div class="tri-col" id="tri-col">
|
page.innerHTML = `<div class="tri-col" id="tri-col"></div>`;
|
||||||
<div class="col" id="col-0"></div><div class="col" id="col-1"></div><div class="col" id="col-2"></div>
|
|
||||||
</div>`;
|
|
||||||
let catalog = [];
|
let catalog = [];
|
||||||
try {
|
try {
|
||||||
catalog = JSON.parse(await bridge.call('fetchCards'));
|
// 优先用预加载缓存,未就绪则实时加载
|
||||||
|
// Prefer the preloaded cache; fetch live when not ready yet
|
||||||
|
catalog = window.preload.cards || JSON.parse(await bridge.call('fetchCards'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 桥接不可用(如未接线)时按空目录处理
|
// 桥接不可用(如未接线)时按空目录处理
|
||||||
// Treat bridge failure as an empty catalog
|
// Treat bridge failure as an empty catalog
|
||||||
@@ -20,27 +20,33 @@ window.updateHomeCards = async function () {
|
|||||||
if (!catalog.some((c) => c.id === 'time')) {
|
if (!catalog.some((c) => c.id === 'time')) {
|
||||||
catalog.push({ id: 'time', name: '大字时间', priority: 0 });
|
catalog.push({ id: 'time', name: '大字时间', priority: 0 });
|
||||||
}
|
}
|
||||||
const cols = cards.layout(catalog);
|
renderCards(catalog);
|
||||||
cols.forEach((ids, i) => {
|
|
||||||
const col = document.getElementById(`col-${i}`);
|
|
||||||
ids.forEach((id) => {
|
|
||||||
if (id === 'time') col.appendChild(renderTimeCard());
|
|
||||||
else col.appendChild(renderGenericCard(id));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// 若有当前媒体,覆盖中间栏为媒体卡(切页回来后恢复媒体卡)
|
|
||||||
// If there is active media, replace the middle column with the media card
|
|
||||||
// (restores the media card when navigating back to home)
|
|
||||||
if (currentMedia) {
|
|
||||||
const mediaCol = document.getElementById('col-1');
|
|
||||||
if (mediaCol) {
|
|
||||||
mediaCol.innerHTML = '';
|
|
||||||
mediaCol.appendChild(renderMediaCard(currentMedia));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
startTimeCardTicker();
|
startTimeCardTicker();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 卡片 Grid 渲染:media 卡 span 2(跨栏),time/其他卡 span 1
|
||||||
|
// Card grid render: media card spans 2 columns, time/other cards span 1
|
||||||
|
function renderCards(catalog) {
|
||||||
|
const grid = document.getElementById('tri-col');
|
||||||
|
grid.textContent = '';
|
||||||
|
const items = [];
|
||||||
|
// 按 priority 排序(数字小者优先)
|
||||||
|
// Sort by priority (smaller first)
|
||||||
|
const sorted = [...catalog].sort((a, b) => (a.priority || 0) - (b.priority || 0));
|
||||||
|
// time 卡(内置,span 1)
|
||||||
|
if (sorted.some(c => c.id === 'time')) items.push({ span: 1, el: renderTimeCard() });
|
||||||
|
// media 卡(有媒体时 span 2,跨栏更美观)
|
||||||
|
if (currentMedia) items.push({ span: 2, el: renderMediaCard(currentMedia) });
|
||||||
|
// 其他卡(排除 time)span 1
|
||||||
|
sorted.filter(c => c.id !== 'time').forEach(c => {
|
||||||
|
items.push({ span: 1, el: renderGenericCard(c.id) });
|
||||||
|
});
|
||||||
|
items.forEach(item => {
|
||||||
|
item.el.classList.add('span-' + item.span);
|
||||||
|
grid.appendChild(item.el);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 内置大字时间卡:当前时间(时:分 + 小号秒)+ 日期
|
// 内置大字时间卡:当前时间(时:分 + 小号秒)+ 日期
|
||||||
// Builtin big time card: current time (HH:MM + small seconds) + date
|
// Builtin big time card: current time (HH:MM + small seconds) + date
|
||||||
function renderTimeCard() {
|
function renderTimeCard() {
|
||||||
@@ -100,16 +106,11 @@ let currentMedia = null;
|
|||||||
window.HearthEvents = window.HearthEvents || {};
|
window.HearthEvents = window.HearthEvents || {};
|
||||||
window.HearthEvents.mediaSessionChanged = function (info) {
|
window.HearthEvents.mediaSessionChanged = function (info) {
|
||||||
currentMedia = info;
|
currentMedia = info;
|
||||||
const mediaCol = document.getElementById('col-1');
|
const grid = document.getElementById('tri-col');
|
||||||
if (!mediaCol) return;
|
if (!grid) return;
|
||||||
if (info) {
|
// 重新渲染:有媒体时 media 卡 span 2,无媒体时其他卡填满
|
||||||
mediaCol.innerHTML = '';
|
// Re-render: media card spans 2 when present, others fill otherwise
|
||||||
mediaCol.appendChild(renderMediaCard(info));
|
|
||||||
} else {
|
|
||||||
// 无媒体:降级到日历/天气(由 cards.layout 重新计算)
|
|
||||||
// No media: fall back to calendar/weather (recomputed by cards.layout)
|
|
||||||
window.updateHomeCards();
|
window.updateHomeCards();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 媒体卡:正在播放标签 + 标题/艺术家 + 进度条
|
// 媒体卡:正在播放标签 + 标题/艺术家 + 进度条
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ window.loadWebAppList = async function () {
|
|||||||
page.innerHTML = `
|
page.innerHTML = `
|
||||||
<div class="search"><input id="web-search" placeholder="搜索 webAPP…"></div>
|
<div class="search"><input id="web-search" placeholder="搜索 webAPP…"></div>
|
||||||
<div class="grid" id="web-grid"></div>`;
|
<div class="grid" id="web-grid"></div>`;
|
||||||
const apps = JSON.parse(await bridge.call('fetchWebApps'));
|
// 优先用预加载缓存,未就绪则实时加载
|
||||||
|
// Prefer the preloaded cache; fetch live when not ready yet
|
||||||
|
const apps = window.preload.webApps || JSON.parse(await bridge.call('fetchWebApps'));
|
||||||
const grid = document.getElementById('web-grid');
|
const grid = document.getElementById('web-grid');
|
||||||
const render = (list) => {
|
const render = (list) => {
|
||||||
grid.textContent = '';
|
grid.textContent = '';
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// 页面数据预加载:启动后后台拉取各页面数据缓存,切页时直接渲染减少延迟
|
||||||
|
// Page data preload: fetch and cache page data in the background after startup,
|
||||||
|
// so page switches render immediately without a fetch delay
|
||||||
|
window.preload = {
|
||||||
|
apps: null,
|
||||||
|
webApps: null,
|
||||||
|
cards: null,
|
||||||
|
|
||||||
|
async loadAll() {
|
||||||
|
try {
|
||||||
|
const [apps, webApps, cards] = await Promise.all([
|
||||||
|
bridge.call('listApps').catch(() => null),
|
||||||
|
bridge.call('fetchWebApps').catch(() => null),
|
||||||
|
bridge.call('fetchCards').catch(() => null),
|
||||||
|
]);
|
||||||
|
this.apps = apps ? JSON.parse(apps) : null;
|
||||||
|
this.webApps = webApps ? JSON.parse(webApps) : null;
|
||||||
|
this.cards = cards ? JSON.parse(cards) : null;
|
||||||
|
} catch (e) {
|
||||||
|
// 预加载失败不阻塞:切页时仍会实时加载
|
||||||
|
// Preload failure is non-blocking: page switches still fetch live
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
package top.yeij.hearth
|
package top.yeij.hearth
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.WallpaperManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
|
import android.util.Base64
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -30,8 +34,10 @@ import top.yeij.hearth.webview.BrightnessProvider
|
|||||||
import top.yeij.hearth.webview.JsBridge
|
import top.yeij.hearth.webview.JsBridge
|
||||||
import top.yeij.hearth.webview.NotificationAccessProvider
|
import top.yeij.hearth.webview.NotificationAccessProvider
|
||||||
import top.yeij.hearth.webview.ServerUrlProvider
|
import top.yeij.hearth.webview.ServerUrlProvider
|
||||||
|
import top.yeij.hearth.webview.WallpaperProvider
|
||||||
import top.yeij.hearth.webview.WebAppHost
|
import top.yeij.hearth.webview.WebAppHost
|
||||||
import top.yeij.hearth.webview.WebViewManager
|
import top.yeij.hearth.webview.WebViewManager
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
// HOME 桌面 activity,接线各能力层:Repository、媒体监听、多 WebView webAPP 管理
|
// HOME 桌面 activity,接线各能力层:Repository、媒体监听、多 WebView webAPP 管理
|
||||||
@@ -106,6 +112,35 @@ class MainActivity : Activity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 壁纸提供实现:WallpaperManager 取当前壁纸转 base64,供 H5 body 背景使用
|
||||||
|
// Wallpaper implementation: WallpaperManager -> base64 for the H5 body background
|
||||||
|
private val wallpaperProvider = object : WallpaperProvider {
|
||||||
|
override fun getWallpaperBase64(): String {
|
||||||
|
return try {
|
||||||
|
val wm = WallpaperManager.getInstance(this@MainActivity)
|
||||||
|
// 动态壁纸(Live Wallpaper)是实时渲染、无静态 Drawable,返回空让 H5 保持
|
||||||
|
// 透明透出动态壁纸;此时玻璃模糊降级为普通半透明(backdrop-filter 无内容可模糊)
|
||||||
|
// Live wallpapers render in real time with no static drawable; return empty
|
||||||
|
// so H5 stays transparent and shows the live wallpaper, with the glass blur
|
||||||
|
// degrading to plain translucency (backdrop-filter has nothing to blur)
|
||||||
|
if (wm.wallpaperInfo != null) return ""
|
||||||
|
val d = wm.drawable ?: return ""
|
||||||
|
val dm = resources.displayMetrics
|
||||||
|
val bmp = Bitmap.createBitmap(dm.widthPixels, dm.heightPixels, Bitmap.Config.ARGB_8888)
|
||||||
|
val canvas = Canvas(bmp)
|
||||||
|
d.setBounds(0, 0, dm.widthPixels, dm.heightPixels)
|
||||||
|
d.draw(canvas)
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
bmp.compress(Bitmap.CompressFormat.JPEG, 80, out)
|
||||||
|
bmp.recycle()
|
||||||
|
"data:image/jpeg;base64," + Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "getWallpaper: ${e.message}")
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private lateinit var webAppRepository: WebAppRepository
|
private lateinit var webAppRepository: WebAppRepository
|
||||||
private lateinit var mediaSource: MediaSessionSource
|
private lateinit var mediaSource: MediaSessionSource
|
||||||
private lateinit var root: FrameLayout
|
private lateinit var root: FrameLayout
|
||||||
@@ -155,6 +190,7 @@ class MainActivity : Activity() {
|
|||||||
notificationAccess = notificationAccess,
|
notificationAccess = notificationAccess,
|
||||||
brightness = brightnessProvider,
|
brightness = brightnessProvider,
|
||||||
serverUrl = serverUrlProvider,
|
serverUrl = serverUrlProvider,
|
||||||
|
wallpaper = wallpaperProvider,
|
||||||
)
|
)
|
||||||
root = FrameLayout(this)
|
root = FrameLayout(this)
|
||||||
setContentView(root)
|
setContentView(root)
|
||||||
|
|||||||
@@ -36,6 +36,14 @@ interface ServerUrlProvider {
|
|||||||
fun setServerUrl(url: String)
|
fun setServerUrl(url: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 壁纸提供接口:由 MainActivity 实现(WallpaperManager 转 base64),
|
||||||
|
// 让壁纸进入 WebView 内部,backdrop-filter 玻璃才能模糊到壁纸
|
||||||
|
// Wallpaper provider implemented by MainActivity (WallpaperManager -> base64), so the
|
||||||
|
// wallpaper lives inside the WebView and the glass backdrop-filter can blur it
|
||||||
|
interface WallpaperProvider {
|
||||||
|
fun getWallpaperBase64(): String
|
||||||
|
}
|
||||||
|
|
||||||
class JsBridge(
|
class JsBridge(
|
||||||
private val deviceWidthPx: Int,
|
private val deviceWidthPx: Int,
|
||||||
private val deviceHeightPx: Int,
|
private val deviceHeightPx: Int,
|
||||||
@@ -58,6 +66,9 @@ class JsBridge(
|
|||||||
// 服务器地址存储接口
|
// 服务器地址存储接口
|
||||||
// Server URL storage provider
|
// Server URL storage provider
|
||||||
private val serverUrl: ServerUrlProvider? = null,
|
private val serverUrl: ServerUrlProvider? = null,
|
||||||
|
// 壁纸提供接口(base64)
|
||||||
|
// Wallpaper provider (base64)
|
||||||
|
private val wallpaper: WallpaperProvider? = null,
|
||||||
) {
|
) {
|
||||||
private val gson = com.google.gson.Gson()
|
private val gson = com.google.gson.Gson()
|
||||||
|
|
||||||
@@ -270,6 +281,11 @@ class JsBridge(
|
|||||||
serverUrl?.setServerUrl(url)
|
serverUrl?.setServerUrl(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 返回壁纸 base64(data URI,未接入返回空字符串)
|
||||||
|
// Return the wallpaper base64 (data URI; empty string when not wired)
|
||||||
|
@android.webkit.JavascriptInterface
|
||||||
|
fun getWallpaper(): String = wallpaper?.getWallpaperBase64() ?: ""
|
||||||
|
|
||||||
// 重新拉取 webAPP 清单 + 卡片目录,返回 { count }
|
// 重新拉取 webAPP 清单 + 卡片目录,返回 { count }
|
||||||
// Re-fetch the webAPP manifest + card catalog, return { count }
|
// Re-fetch the webAPP manifest + card catalog, return { count }
|
||||||
@android.webkit.JavascriptInterface
|
@android.webkit.JavascriptInterface
|
||||||
|
|||||||
Reference in New Issue
Block a user