feat: add notification-access setting item with jump

This commit is contained in:
2026-08-16 18:12:06 +08:00
parent 8e722dc545
commit e2a289a4b0
3 changed files with 64 additions and 2 deletions
+22 -2
View File
@@ -1,5 +1,5 @@
// 设置页:Miuix Preference 分组 + 玻璃条目(仅 UI 骨架,交互留后续 // 设置页:Miuix Preference 分组 + 玻璃条目(通知使用权为可用交互项
// Settings page: Miuix Preference groups + glass items (UI skeleton only; interactions later) // Settings page: Miuix Preference groups + glass items (notification access is interactive)
window.loadSettings = function () { window.loadSettings = function () {
const page = document.querySelector('[data-page="settings"]'); const page = document.querySelector('[data-page="settings"]');
if (page.dataset.loaded) return; page.dataset.loaded = '1'; if (page.dataset.loaded) return; page.dataset.loaded = '1';
@@ -7,10 +7,30 @@ window.loadSettings = function () {
<div class="st-group">显示</div> <div class="st-group">显示</div>
<div class="st-item"><span class="lbl">跟随系统主题</span><span class="sw on"><i></i></span></div> <div class="st-item"><span class="lbl">跟随系统主题</span><span class="sw on"><i></i></span></div>
<div class="st-item"><span class="lbl">屏幕亮度</span><span class="slider"><i></i></span></div> <div class="st-item"><span class="lbl">屏幕亮度</span><span class="slider"><i></i></span></div>
<div class="st-group">权限</div>
<div class="st-item" id="notif-access">
<span class="lbl">通知使用权(媒体卡)</span>
<span class="arrow" id="notif-access-state"></span>
</div>
<div class="st-group">网络</div> <div class="st-group">网络</div>
<div class="st-item"><span class="lbl">webAPP 服务器地址</span><span class="arrow"></span></div> <div class="st-item"><span class="lbl">webAPP 服务器地址</span><span class="arrow"></span></div>
<div class="st-item"><span class="lbl">检查更新</span><span class="arrow"></span></div> <div class="st-item"><span class="lbl">检查更新</span><span class="arrow"></span></div>
<div class="st-group">关于</div> <div class="st-group">关于</div>
<div class="st-item"><span class="lbl">版本 0.1.0</span><span class="arrow"></span></div> <div class="st-item"><span class="lbl">版本 0.1.0</span><span class="arrow"></span></div>
</div>`; </div>`;
setupNotificationAccess(page);
}; };
// 通知使用权授权项:异步查状态,未授权点击跳转系统授权页
// Notification-access item: async state check, tap to open the system grant page
function setupNotificationAccess(page) {
const item = page.querySelector('#notif-access');
const state = page.querySelector('#notif-access-state');
bridge.call('getNotificationAccess').then(granted => {
state.textContent = granted ? '已开启 ✓' : '去开启 ';
if (granted) state.style.color = 'var(--accent)';
}).catch(() => {});
item.onclick = () => {
bridge.call('requestNotificationAccess').catch(() => {});
};
}
@@ -1,8 +1,10 @@
package top.yeij.hearth package top.yeij.hearth
import android.app.Activity import android.app.Activity
import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import android.view.View import android.view.View
@@ -24,6 +26,7 @@ import top.yeij.hearth.webapp.Tab
import top.yeij.hearth.webapp.WebAppContainer import top.yeij.hearth.webapp.WebAppContainer
import top.yeij.hearth.webapp.WebAppRepository import top.yeij.hearth.webapp.WebAppRepository
import top.yeij.hearth.webview.JsBridge import top.yeij.hearth.webview.JsBridge
import top.yeij.hearth.webview.NotificationAccessProvider
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.File import java.io.File
@@ -47,6 +50,19 @@ class MainActivity : Activity() {
private val webAppContainer = WebAppContainer() private val webAppContainer = WebAppContainer()
private val gson = Gson() private val gson = Gson()
private val webAppHost = WebAppHostImpl() private val webAppHost = WebAppHostImpl()
// 通知使用权访问实现:检查授权状态 + 跳转系统授权页(供设置页授权项使用)
// Notification-access implementation: check grant state + jump to system settings
// (used by the settings permission item)
private val notificationAccess = object : NotificationAccessProvider {
override fun isGranted(): Boolean =
NotificationManagerCompat.getEnabledListenerPackages(this@MainActivity)
.contains(packageName)
override fun requestAccess() {
startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
}
}
private lateinit var mediaSource: MediaSessionSource private lateinit var mediaSource: MediaSessionSource
private lateinit var root: FrameLayout private lateinit var root: FrameLayout
private lateinit var desktopWebView: WebView private lateinit var desktopWebView: WebView
@@ -91,6 +107,7 @@ class MainActivity : Activity() {
webAppContainer = webAppContainer, webAppContainer = webAppContainer,
webAppHost = webAppHost, webAppHost = webAppHost,
postToMainThread = { desktopWebView.post(it) }, postToMainThread = { desktopWebView.post(it) },
notificationAccess = notificationAccess,
) )
root = FrameLayout(this) root = FrameLayout(this)
setContentView(root) setContentView(root)
@@ -10,6 +10,15 @@ import top.yeij.hearth.webapp.WebApp
import top.yeij.hearth.webapp.WebAppContainer import top.yeij.hearth.webapp.WebAppContainer
import top.yeij.hearth.webapp.WebAppRepository import top.yeij.hearth.webapp.WebAppRepository
// 通知使用权访问接口:由 MainActivity 实现,JsBridge 通过它检查/申请授权,
// 保持 JsBridge 可 JVM 单测(不直接依赖 Context/NotificationManagerCompat
// Notification-access provider implemented by MainActivity; JsBridge uses it to
// check/request access, keeping JsBridge JVM-testable (no direct Context dependency)
interface NotificationAccessProvider {
fun isGranted(): Boolean
fun requestAccess()
}
class JsBridge( class JsBridge(
private val deviceWidthPx: Int, private val deviceWidthPx: Int,
private val deviceHeightPx: Int, private val deviceHeightPx: Int,
@@ -23,6 +32,9 @@ class JsBridge(
// 主线程执行器:View 操作必须切到主线程;null(单测)时同步执行 // 主线程执行器:View 操作必须切到主线程;null(单测)时同步执行
// Main-thread executor: view ops must run on main; null (unit tests) runs inline // Main-thread executor: view ops must run on main; null (unit tests) runs inline
private val postToMainThread: ((() -> Unit) -> Unit)? = null, private val postToMainThread: ((() -> Unit) -> Unit)? = null,
// 通知使用权访问接口(检查/申请授权),供设置页授权项使用
// Notification-access provider (check/request) for the settings permission item
private val notificationAccess: NotificationAccessProvider? = null,
) { ) {
private val gson = com.google.gson.Gson() private val gson = com.google.gson.Gson()
@@ -185,6 +197,19 @@ class JsBridge(
return gson.toJson(cards) return gson.toJson(cards)
} }
// 返回通知使用权是否已授权(未接入返回 false)
// Return whether notification access is granted (false when not wired)
@android.webkit.JavascriptInterface
fun getNotificationAccess(): Boolean = notificationAccess?.isGranted() ?: false
// 跳转系统「通知使用权」设置页,让用户为媒体卡授权
// Jump to the system notification-access settings page for media-card grant
@android.webkit.JavascriptInterface
fun requestNotificationAccess() {
Log.d("HearthBridge", "requestNotificationAccess called")
notificationAccess?.requestAccess()
}
// 注册媒体会话监听,回调推送给 H5(info 为 null 时推 "null" // 注册媒体会话监听,回调推送给 H5(info 为 null 时推 "null"
// Register media session listener; push callbacks to H5 (push "null" when info is null) // Register media session listener; push callbacks to H5 (push "null" when info is null)
fun setMediaListener(source: MediaSessionSource, webView: android.webkit.WebView) { fun setMediaListener(source: MediaSessionSource, webView: android.webkit.WebView) {