From 31107fddd5839f2f627a8a922eb298286633b7da Mon Sep 17 00:00:00 2001 From: AskaEth Date: Mon, 17 Aug 2026 19:25:43 +0800 Subject: [PATCH] feat: log export to Download --- app/src/main/assets/h5/js/pages/settings.js | 20 ++++++++++++ .../main/java/top/yeij/hearth/MainActivity.kt | 31 +++++++++++++++++++ .../yeij/hearth/webapp/WebAppRepository.kt | 1 + .../java/top/yeij/hearth/webview/JsBridge.kt | 11 +++++++ 4 files changed, 63 insertions(+) diff --git a/app/src/main/assets/h5/js/pages/settings.js b/app/src/main/assets/h5/js/pages/settings.js index 6c23aae..615ecca 100644 --- a/app/src/main/assets/h5/js/pages/settings.js +++ b/app/src/main/assets/h5/js/pages/settings.js @@ -42,6 +42,10 @@ window.loadSettings = function () {
关于
+
+ 导出日志 + +
版本 0.1.0
`; setupTheme(page); @@ -51,6 +55,7 @@ window.loadSettings = function () { setupBrightness(page); setupServerUrl(page); setupCheckUpdate(page); + setupExportLog(page); }; // 判断当前是否深色主题(与 mask.js 逻辑一致) @@ -238,6 +243,21 @@ function setupCheckUpdate(page) { }; } +// 导出日志:点击导出到 Download,显示结果 +// Export log: tap to export to Download, show the result +function setupExportLog(page) { + const item = page.querySelector('#export-log-item'); + const state = page.querySelector('#export-log-state'); + item.onclick = () => { + state.textContent = '导出中…'; + bridge.call('exportLog').then(result => { + state.textContent = result + ' ›'; + }).catch(() => { + state.textContent = '失败 ›'; + }); + }; +} + // 通用滑块拖动:touch 拖动实时回调(保留 click 兼容桌面调试) // Generic draggable slider: touch-drag with live callback (click kept for desktop) function makeSliderDraggable(slider, onRatio) { diff --git a/app/src/main/java/top/yeij/hearth/MainActivity.kt b/app/src/main/java/top/yeij/hearth/MainActivity.kt index c09e573..3420136 100644 --- a/app/src/main/java/top/yeij/hearth/MainActivity.kt +++ b/app/src/main/java/top/yeij/hearth/MainActivity.kt @@ -2,6 +2,7 @@ package top.yeij.hearth import android.app.Activity import android.app.WallpaperManager +import android.content.ContentValues import android.content.Intent import android.content.res.Configuration import android.graphics.Bitmap @@ -10,6 +11,7 @@ import android.graphics.Color import android.graphics.drawable.GradientDrawable import android.net.Uri import android.os.Bundle +import android.provider.MediaStore import android.provider.Settings import android.text.TextUtils import android.util.Base64 @@ -215,6 +217,7 @@ class MainActivity : Activity() { onShowTabPanel = { showNativeTabPanel() }, storage = webAppStorage, onImportOfflinePackage = { id -> startImportOfflinePackage(id) }, + onExportLog = { exportLog() }, ) root = FrameLayout(this) setContentView(root) @@ -436,6 +439,34 @@ class MainActivity : Activity() { // dp to px private fun dp(v: Int): Int = (v * resources.displayMetrics.density).toInt() + // 导出日志到 Download 目录(读 logcat 本进程 + 过滤 Hearth 相关行) + // Export the log to the Download directory (read logcat, filter Hearth lines) + private fun exportLog(): String { + return try { + val process = Runtime.getRuntime().exec(arrayOf("logcat", "-d")) + val log = process.inputStream.bufferedReader().readText() + val filtered = log.lines() + .filter { it.contains("Hearth") } + .joinToString("\n") + val name = "hearth-log-" + System.currentTimeMillis() + ".txt" + val values = ContentValues().apply { + put(MediaStore.Downloads.DISPLAY_NAME, name) + put(MediaStore.Downloads.MIME_TYPE, "text/plain") + put(MediaStore.Downloads.RELATIVE_PATH, "Download/") + } + val uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values) + ?: return "导出失败:无法创建文件" + contentResolver.openOutputStream(uri)?.use { out -> + out.write(filtered.toByteArray()) + } ?: return "导出失败:无法写入" + Log.d(TAG, "exportLog: $name (${filtered.length} chars)") + "已导出:Download/$name" + } catch (e: Exception) { + Log.e(TAG, "exportLog failed: ${e.message}") + "导出失败:${e.message}" + } + } + // 触发 SAF 选择 zip 导入离线包 // Trigger SAF to pick a zip for offline-package import private fun startImportOfflinePackage(id: String) { diff --git a/app/src/main/java/top/yeij/hearth/webapp/WebAppRepository.kt b/app/src/main/java/top/yeij/hearth/webapp/WebAppRepository.kt index 0419129..f0fb72e 100644 --- a/app/src/main/java/top/yeij/hearth/webapp/WebAppRepository.kt +++ b/app/src/main/java/top/yeij/hearth/webapp/WebAppRepository.kt @@ -41,6 +41,7 @@ class WebAppRepository( fun fetchManifest(): List { val body = http.get(manifestUrl) + Log.d(TAG, "fetchManifest: url=$manifestUrl bodyNull=${body == null}") if (body != null) { // 先解析、成功才缓存,避免畸形 body 或空结果被永久缓存 // Parse first, cache only on success (never cache malformed/empty bodies) diff --git a/app/src/main/java/top/yeij/hearth/webview/JsBridge.kt b/app/src/main/java/top/yeij/hearth/webview/JsBridge.kt index 76c655b..b758a1d 100644 --- a/app/src/main/java/top/yeij/hearth/webview/JsBridge.kt +++ b/app/src/main/java/top/yeij/hearth/webview/JsBridge.kt @@ -81,6 +81,9 @@ class JsBridge( // 手动导入离线包回调:由 MainActivity 实现(SAF 选择 zip 后导入) // Manual offline-package import callback implemented by MainActivity (SAF pick + import) private val onImportOfflinePackage: ((String) -> Unit)? = null, + // 导出日志回调:由 MainActivity 实现,返回导出结果描述(文件名/错误) + // Export-log callback implemented by MainActivity, returns a result description + private val onExportLog: (() -> String)? = null, ) { private val gson = com.google.gson.Gson() @@ -185,6 +188,14 @@ class JsBridge( @android.webkit.JavascriptInterface fun hasLocalPackage(id: String): Boolean = storage?.hasLocalPackage(id) ?: false + // 导出日志到 Download 目录,返回结果描述(文件名或错误) + // Export the log to the Download directory, return a result description + @android.webkit.JavascriptInterface + fun exportLog(): String { + Log.d("HearthBridge", "exportLog called") + return onExportLog?.invoke() ?: "日志导出未实现" + } + // 本地离线包版本号(无则空字符串) // Local offline-package version (empty string when absent) @android.webkit.JavascriptInterface