feat(nofitication): Live Updates 自定义设置(状态栏内容/颜色/图标) - #216
Conversation
- 7 项代码问题:链式调用缩进、小图缩放、采样解码、预览缓存、未用 import、文件末尾换行、trackerIcon 统一出口 - 重做 Live Updates 设置页排版:开关/内容/颜色/图标分卡片展示,颜色与图标可视化 - 图标默认项更名合成玉
- 状态栏内容 NONE 显式 setShortCriticalText("") 确保 chip 无文本
- TrackerIconDecoder SVG 输入流用 .use 关闭,异常也确保释放 fd
- ConfigBackupManager 导出清空本地图标路径并回退 CUSTOM 到 DEFAULT;导入丢弃路径并归一化
- 自定义图标缓存:主线程只读缓存,每秒刷新零 IO;首次/变更时 Dispatchers.IO 异步解码并主动刷新通知;防并发重复解码与过期结果;服务销毁取消协程
审阅者指南添加了专门的 Live Updates(实时更新)通知自定义功能,包括新的设置项、UI 和持久化支持,以及对可配置状态栏文本、进度条颜色和追踪图标(包括自定义图片解码与备份/导入处理)的运行时支持。 自定义 Live Update 追踪图标解码的时序图sequenceDiagram
actor User
participant LiveUpdateSettingsView
participant AppSettingsManager
participant TaskExecutionService
participant TrackerIconDecoder
User ->> LiveUpdateSettingsView: pick image via ActivityResultContracts.GetContent
LiveUpdateSettingsView ->> LiveUpdateSettingsView: copy file to filesDir/live_update
LiveUpdateSettingsView ->> AppSettingsManager: setLiveUpdateCustomTrackerPath(path)
loop periodic notification refresh
TaskExecutionService ->> TaskExecutionService: trackerIcon()
TaskExecutionService ->> AppSettingsManager: liveUpdateTrackerIcon.value
TaskExecutionService ->> AppSettingsManager: liveUpdateCustomTrackerPath.value
alt icon type CUSTOM and cache miss
TaskExecutionService ->> TaskExecutionService: scheduleTrackerIconDecode(key, path)
TaskExecutionService ->> TrackerIconDecoder: decode(path)
TrackerIconDecoder -->> TaskExecutionService: Bitmap?
TaskExecutionService ->> TaskExecutionService: trackerIconCache[key] = Bitmap?
TaskExecutionService ->> TaskExecutionService: updateNotification(currentSnapshot())
else other types or cache hit
TaskExecutionService ->> TaskExecutionService: use cached Bitmap or fallback IconCompat
end
end
文件级改动
技巧与命令与 Sourcery 交互
自定义你的体验访问你的 dashboard 来:
获取帮助Original review guide in EnglishReviewer's GuideAdds a dedicated Live Updates notification customization feature, including new settings, UI, and persistence, plus runtime support for configurable status bar text, progress bar colors, and tracker icons (including custom image decoding and backup/import handling). Sequence diagram for custom Live Update tracker icon decodingsequenceDiagram
actor User
participant LiveUpdateSettingsView
participant AppSettingsManager
participant TaskExecutionService
participant TrackerIconDecoder
User ->> LiveUpdateSettingsView: pick image via ActivityResultContracts.GetContent
LiveUpdateSettingsView ->> LiveUpdateSettingsView: copy file to filesDir/live_update
LiveUpdateSettingsView ->> AppSettingsManager: setLiveUpdateCustomTrackerPath(path)
loop periodic notification refresh
TaskExecutionService ->> TaskExecutionService: trackerIcon()
TaskExecutionService ->> AppSettingsManager: liveUpdateTrackerIcon.value
TaskExecutionService ->> AppSettingsManager: liveUpdateCustomTrackerPath.value
alt icon type CUSTOM and cache miss
TaskExecutionService ->> TaskExecutionService: scheduleTrackerIconDecode(key, path)
TaskExecutionService ->> TrackerIconDecoder: decode(path)
TrackerIconDecoder -->> TaskExecutionService: Bitmap?
TaskExecutionService ->> TaskExecutionService: trackerIconCache[key] = Bitmap?
TaskExecutionService ->> TaskExecutionService: updateNotification(currentSnapshot())
else other types or cache hit
TaskExecutionService ->> TaskExecutionService: use cached Bitmap or fallback IconCompat
end
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了 4 个问题,并给出了一些整体反馈:
- 追踪器图标缓存(
trackerIconCache)会在主线程和 IO dispatcher 中同时被访问和修改,但没有做同步处理;建议使用线程安全的数据结构(例如ConcurrentHashMap),或者把所有缓存的读写都限制在单一 dispatcher 中,以避免数据竞争。 - 在
LiveUpdateSettingsView中,TrackerIconDecoder.decode是在 UI 线程的remember(customTrackerPath)中被调用的,但内部会执行文件 IO 和位图解码;可以考虑将这部分工作移到后台 dispatcher 中(例如通过produceState/LaunchedEffect配合Dispatchers.IO),以避免潜在的卡顿。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- 追踪器图标缓存(`trackerIconCache`)会在主线程和 IO dispatcher 中同时被访问和修改,但没有做同步处理;建议使用线程安全的数据结构(例如 `ConcurrentHashMap`),或者把所有缓存的读写都限制在单一 dispatcher 中,以避免数据竞争。
- 在 `LiveUpdateSettingsView` 中,`TrackerIconDecoder.decode` 是在 UI 线程的 `remember(customTrackerPath)` 中被调用的,但内部会执行文件 IO 和位图解码;可以考虑将这部分工作移到后台 dispatcher 中(例如通过 `produceState`/`LaunchedEffect` 配合 `Dispatchers.IO`),以避免潜在的卡顿。
## Individual Comments
### Comment 1
<location path="app/src/main/java/com/aliothmoon/maameow/data/preferences/ConfigBackupManager.kt" line_range="109-110" />
<code_context>
- shizukuLaunchPackage = shizukuLaunchPackage.ifBlank { OFFICIAL_SHIZUKU_PACKAGE }
+ shizukuLaunchPackage = shizukuLaunchPackage.ifBlank { OFFICIAL_SHIZUKU_PACKAGE },
+ liveUpdateCustomTrackerPath = "",
+ liveUpdateTrackerIcon = if (this.liveUpdateTrackerIcon == "custom" && this.liveUpdateCustomTrackerPath.isBlank())
+ "default" else this.liveUpdateTrackerIcon,
)
</code_context>
<issue_to_address>
**issue (bug_risk):** 导入时的归一化逻辑可能会在清空路径的同时,仍保留 `liveUpdateTrackerIcon=custom`,这与注释及清理逻辑的意图不符。
在 `normalizedForImport` 中,`liveUpdateCustomTrackerPath` 总是被设为 `""`,但是图标重置条件依然读取的是原对象中的 `this.liveUpdateCustomTrackerPath.isBlank()`。对于跨设备备份场景,如果 `liveUpdateTrackerIcon == "custom"` 且路径原本非空,则该条件仍然为 false,于是图标仍保持为 `"custom"`,即便路径已经被清空。这与「在从其他设备导入自定义图标时回退到内置图标」的设想相矛盾。你可以只基于 `this.liveUpdateTrackerIcon == "custom"` 来判断(反正路径已无条件清空),或者在规范化路径之后再计算新的图标值。
</issue_to_address>
### Comment 2
<location path="app/src/main/java/com/aliothmoon/maameow/domain/service/TaskExecutionService.kt" line_range="94-95" />
<code_context>
private var progressJob: Job? = null
+ // 自定义图标缓存:key = "custom|$path",value = 解码后的 Bitmap(null 表示解码失败/无效)
+ private val trackerIconCache = mutableMapOf<String, Bitmap?>()
+ private var trackerIconDecodeJob: Job? = null
+
override fun onBind(intent: Intent?): IBinder? = null
</code_context>
<issue_to_address>
**issue (bug_risk):** 可变的 `trackerIconCache` 会在多个 dispatcher 上被访问,而没有同步措施,这可能导致数据竞争。
该缓存在 `trackerIcon()` 中读取(在主线程上构建通知),并在 `scheduleTrackerIconDecode()` 中通过 `Dispatchers.IO` 写入。由于 `mutableMapOf` 不是线程安全的,并发访问可能会引发隐蔽的数据竞争问题。请考虑要么将所有访问限制到单一 dispatcher(例如始终通过 `withContext(Dispatchers.Main)` 访问),要么使用线程安全机制封装它,比如使用同步/`ConcurrentHashMap` 风格的 Map,或者在所有访问外层包一层 `Mutex`。
</issue_to_address>
### Comment 3
<location path="app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/LiveUpdateSettingsView.kt" line_range="459-461" />
<code_context>
+ bottom = MaaDesignTokens.Spacing.md,
+ )
+ ) {
+ val iconBitmap = remember(customTrackerPath) {
+ if (customTrackerPath.isNotEmpty())
+ TrackerIconDecoder.decode(customTrackerPath, targetSize = 72)
+ else null
+ }
</code_context>
<issue_to_address>
**suggestion (performance):** 在组合过程中同步解码自定义追踪器图标,会阻塞主线程。
`TrackerIconDecoder.decode` 可能会访问磁盘并解析 SVG,这些都是从可组合函数在主线程上执行的高开销操作。可以考虑将解码移动到后台 dispatcher 中,并以 state 的形式暴露位图(例如使用 `produceState`/`LaunchedEffect` 配合 `withContext(Dispatchers.IO)`),或者复用共享缓存,以避免在重组或路径变化时重复解码和潜在的卡顿。
建议实现:
```
) {
+ val iconBitmap by produceState<Bitmap?>(initialValue = null, key1 = customTrackerPath) {
+ value = if (customTrackerPath.isNotEmpty()) {
+ withContext(Dispatchers.IO) {
+ TrackerIconDecoder.decode(customTrackerPath, targetSize = 72)
+ }
+ } else {
+ null
+ }
+ }
```
要完整实现该修改,你还需要:
1. 确保在 `LiveUpdateSettingsView.kt` 顶部加入正确的 import:
- `import androidx.compose.runtime.produceState`
- `import kotlinx.coroutines.Dispatchers`
- `import kotlinx.coroutines.withContext`
2. 确认已经导入 `Bitmap`(通常是 `android.graphics.Bitmap`),如果没有,则需要导入;或者如果 `TrackerIconDecoder.decode` 的实际返回类型不是 `Bitmap?`,则需要相应地调整 `produceState` 的泛型类型。
</issue_to_address>
### Comment 4
<location path="app/src/main/java/com/aliothmoon/maameow/domain/service/TaskExecutionService.kt" line_range="300" />
<code_context>
+ AppSettingsManager.LiveUpdateChipContent.PROGRESS -> progressInfo.progressLabel
+ AppSettingsManager.LiveUpdateChipContent.TASK -> activeTaskName
+ AppSettingsManager.LiveUpdateChipContent.LOG -> statusText
+ AppSettingsManager.LiveUpdateChipContent.NONE -> ""
+ }
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 对于 `NONE` 的 chip 内容使用空字符串,可能仍然会创建一个空的 chip,而不是将其隐藏。
对于 `LiveUpdateChipContent.NONE`,你返回的是 `""` 而不是 `null`。根据 `NotificationCompat.ProgressStyle` / `setShortCriticalText` 的具体表现,这仍有可能为一个空 chip 预留空间。如果目标是彻底隐藏该 chip,返回 `null`(并在下游正确处理)会更准确地表达「没有 chip」,也能避免 UI 伪影。
建议实现:
```
AppSettingsManager.LiveUpdateChipContent.BOTH -> when {
progressInfo.progressLabel != null && activeTaskName != null ->
"${progressInfo.progressLabel} $activeTaskName"
progressInfo.progressLabel != null -> progressInfo.progressLabel
activeTaskName != null -> activeTaskName
else -> null
}
AppSettingsManager.LiveUpdateChipContent.PROGRESS -> progressInfo.progressLabel
AppSettingsManager.LiveUpdateChipContent.TASK -> activeTaskName
AppSettingsManager.LiveUpdateChipContent.LOG -> statusText
AppSettingsManager.LiveUpdateChipContent.NONE -> null
```
要完整实现这个建议,任何消费该值的代码(很可能是配置 `NotificationCompat` 的地方,例如 `setShortCriticalText` 或类似方法)都应将 `null` 视为「不显示 chip」:当值为 `null` 时,要么跳过调用 setter,要么显式清除该 chip。确保这些调用点现在接受 `String?`,并且不会对 `null` 渲染一个空 chip。
</issue_to_address>请帮我变得更有用!你可以在每条评论上点 👍 或 👎,我会根据反馈改进后续的代码审查。
Original comment in English
Hey - I've found 4 issues, and left some high level feedback:
- Tracker icon cache (
trackerIconCache) is accessed and mutated from both main and IO dispatcher without synchronization; consider using a thread-safe structure (e.g.,ConcurrentHashMap) or confining all cache reads/writes to a single dispatcher to avoid data races. - In
LiveUpdateSettingsView,TrackerIconDecoder.decodeis called insideremember(customTrackerPath)on the UI thread, but it performs file IO and bitmap decoding; moving this work to a background dispatcher (e.g., viaproduceState/LaunchedEffectwithDispatchers.IO) would avoid potential jank.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Tracker icon cache (`trackerIconCache`) is accessed and mutated from both main and IO dispatcher without synchronization; consider using a thread-safe structure (e.g., `ConcurrentHashMap`) or confining all cache reads/writes to a single dispatcher to avoid data races.
- In `LiveUpdateSettingsView`, `TrackerIconDecoder.decode` is called inside `remember(customTrackerPath)` on the UI thread, but it performs file IO and bitmap decoding; moving this work to a background dispatcher (e.g., via `produceState`/`LaunchedEffect` with `Dispatchers.IO`) would avoid potential jank.
## Individual Comments
### Comment 1
<location path="app/src/main/java/com/aliothmoon/maameow/data/preferences/ConfigBackupManager.kt" line_range="109-110" />
<code_context>
- shizukuLaunchPackage = shizukuLaunchPackage.ifBlank { OFFICIAL_SHIZUKU_PACKAGE }
+ shizukuLaunchPackage = shizukuLaunchPackage.ifBlank { OFFICIAL_SHIZUKU_PACKAGE },
+ liveUpdateCustomTrackerPath = "",
+ liveUpdateTrackerIcon = if (this.liveUpdateTrackerIcon == "custom" && this.liveUpdateCustomTrackerPath.isBlank())
+ "default" else this.liveUpdateTrackerIcon,
)
</code_context>
<issue_to_address>
**issue (bug_risk):** Import normalization may still keep `liveUpdateTrackerIcon=custom` while clearing its path, contrary to the comment and sanitation logic.
In `normalizedForImport`, `liveUpdateCustomTrackerPath` is always set to `""`, but the icon reset condition still reads `this.liveUpdateCustomTrackerPath.isBlank()` from the original object. For a cross-device backup where `liveUpdateTrackerIcon == "custom"` and the path was non-blank, the condition remains false, so the icon stays `"custom"` even though the path is now cleared. That contradicts the intent to fall back to a built-in icon when importing custom icons from another device. You could either base the condition solely on `this.liveUpdateTrackerIcon == "custom"` (given the unconditional path clear) or compute the new icon after normalizing the path.
</issue_to_address>
### Comment 2
<location path="app/src/main/java/com/aliothmoon/maameow/domain/service/TaskExecutionService.kt" line_range="94-95" />
<code_context>
private var progressJob: Job? = null
+ // 自定义图标缓存:key = "custom|$path",value = 解码后的 Bitmap(null 表示解码失败/无效)
+ private val trackerIconCache = mutableMapOf<String, Bitmap?>()
+ private var trackerIconDecodeJob: Job? = null
+
override fun onBind(intent: Intent?): IBinder? = null
</code_context>
<issue_to_address>
**issue (bug_risk):** The mutable `trackerIconCache` is accessed from multiple dispatchers without synchronization, which can cause data races.
The cache is read in `trackerIcon()` (notification-building on the main thread) and written in `scheduleTrackerIconDecode()` on `Dispatchers.IO`. Since `mutableMapOf` isn’t thread-safe, concurrent access can cause subtle data races. Please either confine all access to a single dispatcher (e.g., always via `withContext(Dispatchers.Main)`) or wrap it in a thread-safe mechanism such as a synchronized/`ConcurrentHashMap`-style map or a `Mutex` around all access.
</issue_to_address>
### Comment 3
<location path="app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/LiveUpdateSettingsView.kt" line_range="459-461" />
<code_context>
+ bottom = MaaDesignTokens.Spacing.md,
+ )
+ ) {
+ val iconBitmap = remember(customTrackerPath) {
+ if (customTrackerPath.isNotEmpty())
+ TrackerIconDecoder.decode(customTrackerPath, targetSize = 72)
+ else null
+ }
</code_context>
<issue_to_address>
**suggestion (performance):** Decoding the custom tracker icon synchronously in composition can block the main thread.
`TrackerIconDecoder.decode` may hit disk and parse SVGs, which is expensive work to run from a composable on the main thread. Consider moving the decode to a background dispatcher and exposing the bitmap as state (e.g., `produceState`/`LaunchedEffect` + `withContext(Dispatchers.IO)`), or reusing a shared cache to avoid repeated decoding and potential jank during recomposition or path changes.
Suggested implementation:
```
) {
+ val iconBitmap by produceState<Bitmap?>(initialValue = null, key1 = customTrackerPath) {
+ value = if (customTrackerPath.isNotEmpty()) {
+ withContext(Dispatchers.IO) {
+ TrackerIconDecoder.decode(customTrackerPath, targetSize = 72)
+ }
+ } else {
+ null
+ }
+ }
```
To fully implement this change, you’ll also need to:
1. Ensure you have the correct imports at the top of `LiveUpdateSettingsView.kt`:
- `import androidx.compose.runtime.produceState`
- `import kotlinx.coroutines.Dispatchers`
- `import kotlinx.coroutines.withContext`
2. Make sure `Bitmap` is imported if it is not already (typically `android.graphics.Bitmap`), or adjust the generic type on `produceState` to match the actual return type of `TrackerIconDecoder.decode` if it’s not `Bitmap?`.
</issue_to_address>
### Comment 4
<location path="app/src/main/java/com/aliothmoon/maameow/domain/service/TaskExecutionService.kt" line_range="300" />
<code_context>
+ AppSettingsManager.LiveUpdateChipContent.PROGRESS -> progressInfo.progressLabel
+ AppSettingsManager.LiveUpdateChipContent.TASK -> activeTaskName
+ AppSettingsManager.LiveUpdateChipContent.LOG -> statusText
+ AppSettingsManager.LiveUpdateChipContent.NONE -> ""
+ }
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Using an empty string for `NONE` chip content may still allocate an empty chip instead of hiding it.
For `LiveUpdateChipContent.NONE` you return `""` instead of `null`. Depending on how `NotificationCompat.ProgressStyle` / `setShortCriticalText` behave, this could still allocate space for an empty chip. If the goal is to hide the chip entirely, returning `null` (and handling that downstream) would more accurately express "no chip" and avoid UI artifacts.
Suggested implementation:
```
AppSettingsManager.LiveUpdateChipContent.BOTH -> when {
progressInfo.progressLabel != null && activeTaskName != null ->
"${progressInfo.progressLabel} $activeTaskName"
progressInfo.progressLabel != null -> progressInfo.progressLabel
activeTaskName != null -> activeTaskName
else -> null
}
AppSettingsManager.LiveUpdateChipContent.PROGRESS -> progressInfo.progressLabel
AppSettingsManager.LiveUpdateChipContent.TASK -> activeTaskName
AppSettingsManager.LiveUpdateChipContent.LOG -> statusText
AppSettingsManager.LiveUpdateChipContent.NONE -> null
```
To fully implement the suggestion, any code that consumes this value (likely where `NotificationCompat` is configured, e.g. `setShortCriticalText` or similar) should treat `null` as "no chip": either skip calling the setter when the value is `null`, or explicitly clear the chip in that case. Ensure those call sites now accept a `String?` and do not render an empty chip for `null`.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| AppSettingsManager.LiveUpdateChipContent.PROGRESS -> progressInfo.progressLabel | ||
| AppSettingsManager.LiveUpdateChipContent.TASK -> activeTaskName | ||
| AppSettingsManager.LiveUpdateChipContent.LOG -> statusText | ||
| AppSettingsManager.LiveUpdateChipContent.NONE -> "" |
There was a problem hiding this comment.
suggestion (bug_risk): 对于 NONE 的 chip 内容使用空字符串,可能仍然会创建一个空的 chip,而不是将其隐藏。
对于 LiveUpdateChipContent.NONE,你返回的是 "" 而不是 null。根据 NotificationCompat.ProgressStyle / setShortCriticalText 的具体表现,这仍有可能为一个空 chip 预留空间。如果目标是彻底隐藏该 chip,返回 null(并在下游正确处理)会更准确地表达「没有 chip」,也能避免 UI 伪影。
建议实现:
AppSettingsManager.LiveUpdateChipContent.BOTH -> when {
progressInfo.progressLabel != null && activeTaskName != null ->
"${progressInfo.progressLabel} $activeTaskName"
progressInfo.progressLabel != null -> progressInfo.progressLabel
activeTaskName != null -> activeTaskName
else -> null
}
AppSettingsManager.LiveUpdateChipContent.PROGRESS -> progressInfo.progressLabel
AppSettingsManager.LiveUpdateChipContent.TASK -> activeTaskName
AppSettingsManager.LiveUpdateChipContent.LOG -> statusText
AppSettingsManager.LiveUpdateChipContent.NONE -> null
要完整实现这个建议,任何消费该值的代码(很可能是配置 NotificationCompat 的地方,例如 setShortCriticalText 或类似方法)都应将 null 视为「不显示 chip」:当值为 null 时,要么跳过调用 setter,要么显式清除该 chip。确保这些调用点现在接受 String?,并且不会对 null 渲染一个空 chip。
Original comment in English
suggestion (bug_risk): Using an empty string for NONE chip content may still allocate an empty chip instead of hiding it.
For LiveUpdateChipContent.NONE you return "" instead of null. Depending on how NotificationCompat.ProgressStyle / setShortCriticalText behave, this could still allocate space for an empty chip. If the goal is to hide the chip entirely, returning null (and handling that downstream) would more accurately express "no chip" and avoid UI artifacts.
Suggested implementation:
AppSettingsManager.LiveUpdateChipContent.BOTH -> when {
progressInfo.progressLabel != null && activeTaskName != null ->
"${progressInfo.progressLabel} $activeTaskName"
progressInfo.progressLabel != null -> progressInfo.progressLabel
activeTaskName != null -> activeTaskName
else -> null
}
AppSettingsManager.LiveUpdateChipContent.PROGRESS -> progressInfo.progressLabel
AppSettingsManager.LiveUpdateChipContent.TASK -> activeTaskName
AppSettingsManager.LiveUpdateChipContent.LOG -> statusText
AppSettingsManager.LiveUpdateChipContent.NONE -> null
To fully implement the suggestion, any code that consumes this value (likely where NotificationCompat is configured, e.g. setShortCriticalText or similar) should treat null as "no chip": either skip calling the setter when the value is null, or explicitly clear the chip in that case. Ensure those call sites now accept a String? and do not render an empty chip for null.
- trackerIconCache 改用 ConcurrentHashMap,避免主线程读/IO 写数据竞争 - ConfigBackupManager 导入时 CUSTOM 图标无条件回退 DEFAULT(路径已清空) - 设置页自定义图标预览改 produceState + Dispatchers.IO 异步解码,避免阻塞主线程
关联 Issue
无独立 Issue。需求来源:Live Updates 通知此前为固定样式,用户要求可自定义
状态栏 chip 内容、进度条配色与追踪图标。
变更摘要
(进度+任务名/仅进度/仅任务名/最新日志/不显示)
(PNG/JPG/WebP/GIF/SVG/XML,统一解码器 + AndroidSVG 矢量渲染)
Dispatchers.IO 异步解码,失败自动回退默认图标
验证
git diff --check通过compileDebugKotlin通过(gradle-9.4.1,-x :app:generateAssetManifest)testDebugUnitTest通过assembleRelease通过setRequestPromotedOngoing兼容 Android 14+ 的 OIS 展示路径,minSdk 28 / targetSdk 36截图 / 日志 / 说明
lintDebug:已知 AGP/K2 UAST 在 Gradle Kotlin 脚本上触发findFirCompiledSymbol内部异常,与本次改动无关,未通过关闭校验规避状态栏 chip 显示行为已在 HyperOS 上确认
Checklist
Summary by Sourcery
添加可配置的实时更新通知,可自定义状态栏内容、进度颜色和追踪器图标。
新功能:
错误修复:
增强改进:
构建:
Original summary in English
Summary by Sourcery
Add configurable Live Updates notifications with customizable status-bar content, progress colors, and tracker icons.
New Features:
Bug Fixes:
Enhancements:
Build:
新功能:
错误修复:
增强:
构建:
Original summary in English
Summary by Sourcery
添加可配置的实时更新通知,可自定义状态栏内容、进度颜色和追踪器图标。
新功能:
错误修复:
增强改进:
构建:
Original summary in English
Summary by Sourcery
Add configurable Live Updates notifications with customizable status-bar content, progress colors, and tracker icons.
New Features:
Bug Fixes:
Enhancements:
Build: