Skip to content

feat: Chinese localization, MIUI fixes, boot stability & performance optimization#22

Open
xunnv wants to merge 7 commits into
jonathanklee:mainfrom
xunnv:main
Open

feat: Chinese localization, MIUI fixes, boot stability & performance optimization#22
xunnv wants to merge 7 commits into
jonathanklee:mainfrom
xunnv:main

Conversation

@xunnv
Copy link
Copy Markdown

@xunnv xunnv commented May 23, 2026

Summary

This PR brings comprehensive improvements to VolumeLockr, focusing on Chinese localization, MIUI compatibility fixes, boot stability, and performance optimization.

Added

  • Complete Simplified Chinese (zh-CN) translation
  • Dynamic hide-from-recents via ActivityManager.AppTask.setExcludeFromRecents() (fixes MIUI override of Intent flags)
  • Structured logging (VolumeLockr tag) for boot-time debugging
  • Bilingual README, CONTRIBUTING.md, CHANGELOG.md, CODE_OF_CONDUCT.md
  • GitHub issue templates and PR template

Fixed

  • Boot crash loop: ForegroundServiceStartNotAllowedExceptionstopSelf()START_STICKY caused infinite restart on Android 12+
  • Background lock failure: Service destroyed on unbind due to missing startService()
  • MIUI recents hiding: Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS is overridden by MIUI/HyperOS
  • Boot lag: Premature audio HAL access during first 60 seconds after reboot

Performance

  • Replaced 25ms Timer polling with ScheduledExecutorService (500ms interval, 80% CPU reduction)
  • Removed ContentObservercheckVolumes() chain (eliminated 12 premature boot calls)
  • notifyItemChanged(pos) instead of notifyDataSetChanged()
  • Cached isPasswordProtected(), O(1) lock lookup, Gson singleton reuse

Security

  • Removed hardcoded keystore credentials from build.gradle
  • Credentials now read from external release/keystore.properties (git-ignored)

Tested on: Xiaomi Redmi (MIUI 14 / HyperOS), Android 14

Closes: Boot auto-start issues, MIUI recents hiding, background volume lock reliability

Summary by CodeRabbit

  • New Features

    • Simplified Chinese localization added
    • Full scheduling UI: timed slots, import/export, temporary unlocks
    • "Hide from recent tasks" toggle in settings
  • Enhancements

    • More reliable boot/startup and background locking with tuned polling
    • Adapter/UI optimizations to reduce full refreshes and redundant reads
  • Documentation

    • PR/issue templates, CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, CHANGELOG, README added/rewritten
  • Chores

    • Expanded .gitignore, .editorconfig, CI/tooling and build configuration updates

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds documentation and CI templates, updates build tooling and signing, introduces a scheduling subsystem (data, manager, UI, import/export), adds a hide-recents preference with MainActivity API, hardens BootReceiver and VolumeService startup and polling, and updates adapters/fragments and localized strings (including Simplified Chinese).

Changes

Repository docs and templates

Layer / File(s) Summary
Docs, templates, metadata
README.md, CONTRIBUTING.md, CHANGELOG.md, CODE_OF_CONDUCT.md, SECURITY.md, .editorconfig, .gitignore, .github/*
Adds full project docs, PR/issue templates, editorconfig, expanded .gitignore, and security/funding metadata.

CI / Issue / Funding

Layer / File(s) Summary
Issue & PR templates, CI, FUNDING
.github/ISSUE_TEMPLATE/*, .github/pull_request_template.md, .github/workflows/android.yml, .github/FUNDING.yml
Adds bug/feature issue forms, PR template, updates CI action versions, and simplifies FUNDING entries.

Build tooling and signing

Layer / File(s) Summary
Gradle/tooling/signing
build.gradle, app/build.gradle, gradle.properties, gradle/libs.versions.toml
Removes pinned Kotlin extra, bumps Detekt, updates Java/Kotlin targets to 17, conditional release signing via release/keystore.properties, toggles nonTransitiveRClass, and bumps a version catalog entry.

Manifest, navigation, and resources

Layer / File(s) Summary
Manifest/navigation/resources
app/src/main/AndroidManifest.xml, app/src/main/res/navigation/*, app/src/main/res/menu/*, app/src/main/res/values/strings.xml, app/src/main/res/values-zh/strings.xml, app/src/main/res/xml/root_preferences.xml
Sets excludeFromRecents on MainActivity, adds schedule navigation/menu entries, inserts hide-recents strings and preference wiring, and adds Simplified Chinese localization including schedule strings.

Recents preference and Settings

Layer / File(s) Summary
Hide-recents preference & SettingsFragment
app/src/main/java/.../SettingsFragment.kt, app/src/main/res/xml/root_preferences.xml
Adds HIDE_RECENTS_PREFERENCE, SwitchPreferenceCompat wiring, extracted setup helpers, and change listener that calls MainActivity to apply recents exclusion.

MainActivity behavior

Layer / File(s) Summary
Recents API, insets, navigation, DND
app/src/main/java/.../MainActivity.kt
Adds delayed recents exclusion via Handler, public setExcludeFromRecents, simplified window-insets handling, streamlined bottom navigation wiring, and early-return DND permission path.

Scheduling: model, manager, UI

Layer / File(s) Summary
Schedule model
app/src/main/java/.../schedule/ScheduleData.kt
Adds DayType, TimeSlot, ScheduleConfig, and a calendar-day helper for day-of-week mapping.
ScheduleManager
app/src/main/java/.../schedule/ScheduleManager.kt
Implements singleton persistence, 1s in-memory cache, active-slot computation, temporary-unlock helpers, and JSON import/export.
Schedule UI & adapter
app/src/main/java/.../ui/ScheduleFragment.kt, ScheduleAdapter.kt, app/src/main/res/layout/*, app/src/main/res/menu/*
Adds ScheduleFragment UI (master switch, RecyclerView, add/edit dialog with per-stream sliders), ScheduleAdapter, layouts, import/export flows, and menu/navigation integration.

Boot and service startup, polling

Layer / File(s) Summary
BootReceiver & VolumeService
app/src/main/java/.../BootReceiver.kt, app/src/main/java/.../service/VolumeService.kt
BootReceiver checks manual locks and schedule config and starts VolumeService with EXTRA_FROM_BOOT. VolumeService moves from Timer to ScheduledExecutorService, adds gson reuse, polling constants and boot delay, defers polling, prefers schedule targets with temporary-unlock handling, rate-limits logs, downgrades on foreground-start failure, and shuts down executor on destroy.

Adapters, fragments, temporary-unlock

Layer / File(s) Summary
VolumeAdapter & VolumeSliderFragment
app/src/main/java/.../ui/VolumeAdapter.kt, VolumeSliderFragment.kt
VolumeAdapter caches password flag, performs per-item updates, and adds a temporary-unlock dialog flow; VolumeSliderFragment starts locking when bound if service indicates locks or active schedules and unregisters listeners on unbind.

Tests and fastlane metadata

Layer / File(s) Summary
Test cleanup & fastlane
app/src/androidTest/*, app/src/test/*, fastlane/metadata/android/*
Removes example unit/instrumented tests and clears several Fastlane metadata description/title files.

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs:

Suggested reviewers:

  • jonathanklee

Poem:

I'm a rabbit in the code tree hop,
I nudge the docs and mend the clock,
I hide recents with a tiny hop,
schedule the quiet, fix the lock.
Cheers to builds and strings — ko-fi cup, don't stop!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects multiple major changes (Chinese localization, MIUI fixes, boot stability improvements, and performance optimization), making it directly related to the comprehensive changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/res/values-zh/strings.xml`:
- Line 1: The locale folder `values-zh` is too generic for Simplified Chinese;
rename or move this `strings.xml` into a region/script-specific qualifier such
as `values-zh-rCN` or `values-b+zh+Hans` so the translations are scoped to
Simplified Chinese (Hans) only; update any references or merge conflicts
accordingly and keep the same <resources> contents (no content changes needed
inside the `strings.xml`) so Traditional Chinese locales are not served the
Simplified strings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9593d1d6-7e95-4fee-84a1-bbb58dce7af5

📥 Commits

Reviewing files that changed from the base of the PR and between c2793a7 and 65f10f7.

📒 Files selected for processing (1)
  • app/src/main/res/values-zh/strings.xml

@@ -0,0 +1,25 @@
<resources>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Scope this locale to Simplified Chinese explicitly (zh-CN/Hans).

The translations are Simplified Chinese, but values-zh is a generic Chinese bucket. This can surface Simplified text to users expecting Traditional Chinese. Please move this to a region/script-specific qualifier (for example values-zh-rCN or values-b+zh+Hans).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/res/values-zh/strings.xml` at line 1, The locale folder
`values-zh` is too generic for Simplified Chinese; rename or move this
`strings.xml` into a region/script-specific qualifier such as `values-zh-rCN` or
`values-b+zh+Hans` so the translations are scoped to Simplified Chinese (Hans)
only; update any references or merge conflicts accordingly and keep the same
<resources> contents (no content changes needed inside the `strings.xml`) so
Traditional Chinese locales are not served the Simplified strings.

…mization

- i18n(zh): add complete Simplified Chinese translation
- fix(boot): resolve ForegroundServiceStartNotAllowedException death loop
  - Replace stopSelf() with mForegroundFailed flag + START_NOT_STICKY
  - Add 60s boot delay to avoid audio HAL initialization contention
- fix(miui): use AppTask.setExcludeFromRecents for reliable recents hiding
  - Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS is overridden by MIUI
- fix(bg): restore VolumeService.start() to keep service alive after unbind
- perf(service): replace 25ms Timer with ScheduledExecutorService
  - 500ms interval, 80% CPU reduction vs original polling
  - Remove ContentObserver->checkVolumes() chain (12 premature boot calls)
- perf(ui): notifyItemChanged instead of notifyDataSetChanged
- perf(adapter): cache isPasswordProtected, O(1) lock lookup via HashMap
- perf(gson): reuse Gson singleton instance (avoid reflection cache rebuild)
- security: remove hardcoded keystore credentials from build.gradle
  - Read from external release/keystore.properties (git-ignored)
- docs: bilingual README, CONTRIBUTING.md, CHANGELOG.md, CODE_OF_CONDUCT.md
- docs: GitHub issue templates (bug report, feature request) and PR template
- chore: clean up redundant APKs and unused assets
- chore: remove deprecated buildToolsVersion and unused ext.kotlin_version
@xunnv xunnv changed the title Add Simplified Chinese translation feat: Chinese localization, MIUI fixes, boot stability & performance optimization May 24, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (3)
app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt (3)

77-80: ⚡ Quick win

Target the current task instead of assuming appTasks[0].

appTasks ordering is not a stable contract. Match by taskId before calling setExcludeFromRecents.

Proposed fix
-            val tasks = am.appTasks
-            if (tasks.isNotEmpty()) {
-                tasks[0].setExcludeFromRecents(exclude)
-            }
+            am.appTasks
+                .firstOrNull { it.taskInfo?.taskId == taskId }
+                ?.setExcludeFromRecents(exclude)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt` around lines 77 -
80, The current code assumes am.appTasks[0] is the current task; instead iterate
am.appTasks to find the TaskInfo whose taskId matches the current activity's
taskId (use this.taskId or Activity.getTaskId()) and call
setExcludeFromRecents(exclude) only on that matched task; update the block in
MainActivity where variables tasks, am and exclude are used to perform a safe
lookup by taskId rather than using tasks[0].

97-98: ⚡ Quick win

Align @RequiresApi contract with actual implementation.

setupWindowInsets() currently uses compat APIs but is marked as Q+ only and called unguarded. Either remove the annotation or gate the call at Q+.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt` around lines 97 -
98, The `@RequiresApi`(Build.VERSION_CODES.Q) annotation on setupWindowInsets() is
incorrect because the method uses compat APIs yet is invoked without a runtime
guard; either remove the `@RequiresApi` annotation from setupWindowInsets() so it
can be called on all API levels, or keep the annotation and add a runtime
API-level check (e.g., Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) at each
call site that invokes setupWindowInsets(); update the symbol setupWindowInsets
and any call sites accordingly so the annotation and runtime behavior are
consistent.

26-27: ⚡ Quick win

Clear delayed callbacks on destroy to avoid stale execution.

postDelayed can execute after the activity is torn down. Clear pending callbacks in onDestroy().

Proposed fix
 class MainActivity : AppCompatActivity() {
@@
     private val handler = Handler(Looper.getMainLooper())
@@
+    override fun onDestroy() {
+        handler.removeCallbacksAndMessages(null)
+        super.onDestroy()
+    }
+
     private fun applyRecentsHiding() {

Also applies to: 64-67

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt` around lines 26 -
27, The Activity posts delayed work with the Handler declared as "handler" (see
uses of handler.postDelayed around lines 64-67); this can fire after the
Activity is destroyed—add cleanup in MainActivity.onDestroy() to remove pending
callbacks/messages. In onDestroy(), call
handler.removeCallbacksAndMessages(null) (or removeCallbacks for specific
Runnables referenced where handler.postDelayed is used) to cancel any pending
delayed tasks before calling super.onDestroy().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/pull_request_template.md:
- Around line 1-68: The PR template is written as a GitHub issue-form YAML (keys
like name:, about:, body:, type:, id:) which GitHub does not render for pull
requests; replace the YAML with a plain Markdown PR template: add a short
title/description section, headings for "Type of change", "Description",
"Testing", "Checklist" and "Related issue", and convert the YAML
checkboxes/options into a Markdown task list (e.g., - [ ] I have read
CONTRIBUTING.md) and simple placeholders for type and testing details so
contributors see a normal editable PR body instead of raw YAML.

In `@app/build.gradle`:
- Around line 45-55: The keystore loading in app/build.gradle uses
keystoreProperties.load(new FileInputStream(...)) without closing the stream,
doesn't validate required keys, and silently skips signing if the keystore file
is missing; fix by opening the properties file with a try-with-resources style
(use FileInputStream in a .withCloseable or try/finally to close), validate that
keystoreProperties['storeFile'], ['storePassword'], ['keyAlias'], and
['keyPassword'] are non-null/non-blank before applying them to
signingConfigs.release, and if properties are present but the resolved
keystoreFile does not exist fail fast (throw GradleException or logger.error +
throw) instead of silently skipping so build configuration errors are visible;
update the block that references keystorePropertiesFile, keystoreProperties,
signingConfigs.release, and buildTypes.release accordingly.

In `@app/src/main/java/com/klee/volumelockr/service/VolumeService.kt`:
- Around line 263-266: The comment above invokeVolumeListenerCallback()
incorrectly states a "100ms" response while the actual polling interval is
500ms; update the comment to reflect the real interval (500ms) or change the
polling interval constant to 100ms if you intended faster response. Locate the
polling loop that calls invokeVolumeListenerCallback() and either (A) edit the
comment text to say "500ms" (or reference the polling constant name used, e.g.,
pollingIntervalMs / VOLUME_POLL_INTERVAL_MS) or (B) change the poll interval
constant to 100ms so the comment matches; ensure the comment and the value
referenced by the polling loop remain consistent.
- Around line 140-143: The comment describing the boot delay is inconsistent
with the constant value: it says "延迟 15s 启动" but BOOT_DELAY_MS is 60000L (60s).
Update either the comment or the constant so they match; e.g., if intended delay
is 15s, change BOOT_DELAY_MS to 15000L, or if 60s is correct, change the comment
text to "延迟 60s 启动". Make the edit near the BOOT_DELAY_MS declaration and the
usage in initialDelay/startLocking so both the constant and the inline comment
(lines around BOOT_DELAY_MS, initialDelay, fromBoot, startLocking) reflect the
same delay value.
- Around line 153-171: The KDoc for startLocking incorrectly states a 100ms
polling interval while the code uses POLL_INTERVAL_MS (500ms); update the
documentation to match the actual interval (use POLL_INTERVAL_MS value or
"500ms") or change POLL_INTERVAL_MS if 100ms is intended. Locate the
startLocking function and its KDoc, adjust the text "100ms 间隔轮询" to accurately
reflect POLL_INTERVAL_MS (e.g., "500ms 间隔轮询" or reference POLL_INTERVAL_MS) so
the comment and implementation are consistent.

In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt`:
- Around line 97-100: In onBindViewHolder (VolumeAdapter.kt) the code only sets
UI for locked rows, causing recycled holders to keep prior checked/disabled
states; add an explicit else branch when checking
mService?.getLocks()?.containsKey(volume.stream) to reset the UI for unlocked
rows by setting holder.binding.switchButton.isChecked = false and
holder.binding.slider.isEnabled = true (reference the existing
holder.binding.switchButton and holder.binding.slider to locate where to add the
else).
- Around line 33-44: In update(volumes: List<Volume>) capture the old size
(mVolumeList.size) before assigning mVolumeList = volumes, then handle size
changes first: if volumes.size > oldSize call notifyItemRangeInserted(oldSize,
volumes.size - oldSize) (or notifyItemInserted per index) for new items; if
volumes.size < oldSize call notifyItemRangeRemoved(volumes.size, oldSize -
volumes.size) (or notifyItemRemoved per index) for removed items; after handling
inserts/removes compute changed positions only over the overlapping range (0
until min(oldSize, volumes.size)) comparing volumes[i].value/locked with
mVolumeList[i] and call notifyItemChanged(i) for those indices; finally assign
mVolumeList = volumes. This ensures insertions/removals are announced before
per-item change notifications.

In `@CODE_OF_CONDUCT.md`:
- Around line 57-60: The enforcement contact is still a placeholder in the
sentence starting "Instances of abusive, harassing, or otherwise unacceptable
behavior may be reported to the community leaders responsible for enforcement at
the repository owner's contact information."; replace that placeholder with a
concrete contact method (for example a maintainer/security email like
security@your-org.com or a link to the repo's issues/discussions route or a
pointer to SECURITY.md) so reporters have an actionable channel; update the same
sentence in CODE_OF_CONDUCT.md to include that real email or specific
issue/discussion route and optionally mention expected response/timeframe.

In `@CONTRIBUTING.md`:
- Line 143: Update the malformed BCP 47 example under the "Locale codes" section
by replacing the incorrect tag `ru-rRU` with the correct `ru-RU`; ensure the
example list (`zh`, `fr`, `ru-RU`) follows BCP 47 casing/format rules (or add a
separate note if you intend to document Android resource qualifier format
separately).
- Line 41: Add blank lines before and after the Markdown table that starts with
"| Prefix | Purpose |" so it doesn't trigger MD058, and annotate any fenced code
blocks mentioned at lines 73, 98, 109, and 149 with an explicit language (e.g.,
use ```text or ```bash as appropriate) to resolve MD040; update the
CONTRIBUTING.md by locating the table header string "| Prefix | Purpose |" and
the fenced code blocks at the referenced locations and insert a blank line above
and below the table and add the language specifier to each fence.

---

Nitpick comments:
In `@app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt`:
- Around line 77-80: The current code assumes am.appTasks[0] is the current
task; instead iterate am.appTasks to find the TaskInfo whose taskId matches the
current activity's taskId (use this.taskId or Activity.getTaskId()) and call
setExcludeFromRecents(exclude) only on that matched task; update the block in
MainActivity where variables tasks, am and exclude are used to perform a safe
lookup by taskId rather than using tasks[0].
- Around line 97-98: The `@RequiresApi`(Build.VERSION_CODES.Q) annotation on
setupWindowInsets() is incorrect because the method uses compat APIs yet is
invoked without a runtime guard; either remove the `@RequiresApi` annotation from
setupWindowInsets() so it can be called on all API levels, or keep the
annotation and add a runtime API-level check (e.g., Build.VERSION.SDK_INT >=
Build.VERSION_CODES.Q) at each call site that invokes setupWindowInsets();
update the symbol setupWindowInsets and any call sites accordingly so the
annotation and runtime behavior are consistent.
- Around line 26-27: The Activity posts delayed work with the Handler declared
as "handler" (see uses of handler.postDelayed around lines 64-67); this can fire
after the Activity is destroyed—add cleanup in MainActivity.onDestroy() to
remove pending callbacks/messages. In onDestroy(), call
handler.removeCallbacksAndMessages(null) (or removeCallbacks for specific
Runnables referenced where handler.postDelayed is used) to cancel any pending
delayed tasks before calling super.onDestroy().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 908b14c0-b330-4576-be27-654219a00453

📥 Commits

Reviewing files that changed from the base of the PR and between 65f10f7 and 9e37e8f.

⛔ Files ignored due to path filters (2)
  • app/src/main/ic_volumelockr-playstore.png is excluded by !**/*.png
  • app/src/main/screenshot.png is excluded by !**/*.png
📒 Files selected for processing (20)
  • .github/ISSUE_TEMPLATE/bug_report.yml
  • .github/ISSUE_TEMPLATE/feature_request.yml
  • .github/pull_request_template.md
  • .gitignore
  • CHANGELOG.md
  • CODE_OF_CONDUCT.md
  • CONTRIBUTING.md
  • README.md
  • app/build.gradle
  • app/src/main/AndroidManifest.xml
  • app/src/main/java/com/klee/volumelockr/BootReceiver.kt
  • app/src/main/java/com/klee/volumelockr/service/VolumeService.kt
  • app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt
  • app/src/main/java/com/klee/volumelockr/ui/SettingsFragment.kt
  • app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt
  • app/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.kt
  • app/src/main/res/values-zh/strings.xml
  • app/src/main/res/values/strings.xml
  • app/src/main/res/xml/root_preferences.xml
  • build.gradle
💤 Files with no reviewable changes (1)
  • build.gradle
✅ Files skipped from review due to trivial changes (6)
  • app/src/main/res/xml/root_preferences.xml
  • .github/ISSUE_TEMPLATE/bug_report.yml
  • .github/ISSUE_TEMPLATE/feature_request.yml
  • CHANGELOG.md
  • README.md
  • app/src/main/res/values-zh/strings.xml

Comment thread .github/pull_request_template.md Outdated
Comment thread app/build.gradle
Comment on lines +45 to +55
if (keystorePropertiesFile.exists()) {
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
def keystoreFile = rootProject.file(keystoreProperties['storeFile'])
if (keystoreFile.exists()) {
signingConfigs.release.storeFile = keystoreFile
signingConfigs.release.storePassword = keystoreProperties['storePassword']
signingConfigs.release.keyAlias = keystoreProperties['keyAlias']
signingConfigs.release.keyPassword = keystoreProperties['keyPassword']
buildTypes.release.signingConfig = signingConfigs.release
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
sed -n '44,56p' app/build.gradle

printf '\n-- direct property lookups --\n'
rg -n "keystoreProperties\\['(storeFile|storePassword|keyAlias|keyPassword)'\\]" app/build.gradle

printf '\n-- existing validation / scoped-close checks --\n'
rg -n "withInputStream|withCloseable|GradleException|missingKeys|containsKey" app/build.gradle

Repository: jonathanklee/VolumeLockr

Length of output: 1253


🏁 Script executed:

#!/bin/bash
rg -n "FileInputStream|withInputStream|withCloseable|close\\(" app/build.gradle

Repository: jonathanklee/VolumeLockr

Length of output: 148


Fail fast & properly scope keystore signing property loading (app/build.gradle)

  • app/build.gradle loads keystoreProperties with keystoreProperties.load(new FileInputStream(...)) without scoping/closing the stream.
  • Required keys (storeFile, storePassword, keyAlias, keyPassword) are accessed directly; missing/blank values can fail during Gradle configuration with opaque errors.
  • If the referenced keystore file doesn’t exist, signing is silently skipped, making release signing issues easy to miss.
Proposed fix
     if (keystorePropertiesFile.exists()) {
         def keystoreProperties = new Properties()
-        keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
-        def keystoreFile = rootProject.file(keystoreProperties['storeFile'])
-        if (keystoreFile.exists()) {
-            signingConfigs.release.storeFile = keystoreFile
-            signingConfigs.release.storePassword = keystoreProperties['storePassword']
-            signingConfigs.release.keyAlias = keystoreProperties['keyAlias']
-            signingConfigs.release.keyPassword = keystoreProperties['keyPassword']
-            buildTypes.release.signingConfig = signingConfigs.release
-        }
+        keystorePropertiesFile.withInputStream { stream ->
+            keystoreProperties.load(stream)
+        }
+
+        def requiredKeys = ['storeFile', 'storePassword', 'keyAlias', 'keyPassword']
+        def missingKeys = requiredKeys.findAll { !keystoreProperties[it] }
+        if (!missingKeys.isEmpty()) {
+            throw new GradleException(
+                "Invalid release/keystore.properties. Missing: ${missingKeys.join(', ')}"
+            )
+        }
+
+        def keystoreFile = rootProject.file(keystoreProperties['storeFile'])
+        if (!keystoreFile.exists()) {
+            throw new GradleException("Keystore file not found: ${keystoreFile}")
+        }
+
+        signingConfigs.release.storeFile = keystoreFile
+        signingConfigs.release.storePassword = keystoreProperties['storePassword']
+        signingConfigs.release.keyAlias = keystoreProperties['keyAlias']
+        signingConfigs.release.keyPassword = keystoreProperties['keyPassword']
+        buildTypes.release.signingConfig = signingConfigs.release
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/build.gradle` around lines 45 - 55, The keystore loading in
app/build.gradle uses keystoreProperties.load(new FileInputStream(...)) without
closing the stream, doesn't validate required keys, and silently skips signing
if the keystore file is missing; fix by opening the properties file with a
try-with-resources style (use FileInputStream in a .withCloseable or try/finally
to close), validate that keystoreProperties['storeFile'], ['storePassword'],
['keyAlias'], and ['keyPassword'] are non-null/non-blank before applying them to
signingConfigs.release, and if properties are present but the resolved
keystoreFile does not exist fail fast (throw GradleException or logger.error +
throw) instead of silently skipping so build configuration errors are visible;
update the block that references keystorePropertiesFile, keystoreProperties,
signingConfigs.release, and buildTypes.release accordingly.

Comment thread app/src/main/java/com/klee/volumelockr/service/VolumeService.kt
Comment on lines +153 to 171
/**
* 启动音量锁定:开启 100ms 间隔轮询。
* @param initialDelayMs 首次轮询延迟,0 = 立即,>0 = 延迟毫秒数(如开机场景)
*/
@Synchronized
fun startLocking() {
if (mTimer != null) {
fun startLocking(initialDelayMs: Long = 0) {
if (mPollingFuture != null) {
Log.d(TAG, "startLocking: 已在轮询中,跳过 (callDelay=${initialDelayMs}ms)")
return
}

mTimer = Timer()
mTimer?.scheduleAtFixedRate(
object : TimerTask() {
override fun run() {
checkVolumes()
}
},
0,
PERIOD_IN_MS
mCheckCount = 0
Log.i(TAG, "startLocking: 启动轮询, initialDelay=${initialDelayMs}ms, interval=${POLL_INTERVAL_MS}ms, locks=${mVolumeLock.size}")
mPollingFuture = mCheckExecutor.scheduleWithFixedDelay(
{ checkVolumes() },
initialDelayMs,
POLL_INTERVAL_MS,
TimeUnit.MILLISECONDS
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Javadoc inconsistency: says 100ms but interval is 500ms.

The Javadoc on line 154 states "100ms 间隔轮询" (100ms interval polling), but POLL_INTERVAL_MS is 500L (500ms).

Proposed fix
     /**
-     * 启动音量锁定:开启 100ms 间隔轮询。
+     * 启动音量锁定:开启 500ms 间隔轮询。
      * `@param` initialDelayMs 首次轮询延迟,0 = 立即,>0 = 延迟毫秒数(如开机场景)
      */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* 启动音量锁定:开启 100ms 间隔轮询。
* @param initialDelayMs 首次轮询延迟,0 = 立即,>0 = 延迟毫秒数(如开机场景)
*/
@Synchronized
fun startLocking() {
if (mTimer != null) {
fun startLocking(initialDelayMs: Long = 0) {
if (mPollingFuture != null) {
Log.d(TAG, "startLocking: 已在轮询中,跳过 (callDelay=${initialDelayMs}ms)")
return
}
mTimer = Timer()
mTimer?.scheduleAtFixedRate(
object : TimerTask() {
override fun run() {
checkVolumes()
}
},
0,
PERIOD_IN_MS
mCheckCount = 0
Log.i(TAG, "startLocking: 启动轮询, initialDelay=${initialDelayMs}ms, interval=${POLL_INTERVAL_MS}ms, locks=${mVolumeLock.size}")
mPollingFuture = mCheckExecutor.scheduleWithFixedDelay(
{ checkVolumes() },
initialDelayMs,
POLL_INTERVAL_MS,
TimeUnit.MILLISECONDS
)
}
/**
* 启动音量锁定:开启 500ms 间隔轮询。
* `@param` initialDelayMs 首次轮询延迟,0 = 立即,>0 = 延迟毫秒数(如开机场景)
*/
`@Synchronized`
fun startLocking(initialDelayMs: Long = 0) {
if (mPollingFuture != null) {
Log.d(TAG, "startLocking: 已在轮询中,跳过 (callDelay=${initialDelayMs}ms)")
return
}
mCheckCount = 0
Log.i(TAG, "startLocking: 启动轮询, initialDelay=${initialDelayMs}ms, interval=${POLL_INTERVAL_MS}ms, locks=${mVolumeLock.size}")
mPollingFuture = mCheckExecutor.scheduleWithFixedDelay(
{ checkVolumes() },
initialDelayMs,
POLL_INTERVAL_MS,
TimeUnit.MILLISECONDS
)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/service/VolumeService.kt` around lines
153 - 171, The KDoc for startLocking incorrectly states a 100ms polling interval
while the code uses POLL_INTERVAL_MS (500ms); update the documentation to match
the actual interval (use POLL_INTERVAL_MS value or "500ms") or change
POLL_INTERVAL_MS if 100ms is intended. Locate the startLocking function and its
KDoc, adjust the text "100ms 间隔轮询" to accurately reflect POLL_INTERVAL_MS (e.g.,
"500ms 间隔轮询" or reference POLL_INTERVAL_MS) so the comment and implementation
are consistent.

Comment on lines +263 to 266
// 仅通知 UI 更新,不触发 checkVolumes()。
// 轮询循环 100ms 已足够快速响应(<0.1s 延迟,人无法感知),
// ContentObserver 触发检查会在开机初期引发 12 次音频 HAL 争抢导致卡顿。
invokeVolumeListenerCallback()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Comment inconsistency: says 100ms but interval is 500ms.

The comment references "100ms" response time, but the actual polling interval is 500ms.

Proposed fix
-            // 轮询循环 100ms 已足够快速响应(<0.1s 延迟,人无法感知),
+            // 轮询循环 500ms 已足够快速响应(<0.5s 延迟,人无法感知),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/service/VolumeService.kt` around lines
263 - 266, The comment above invokeVolumeListenerCallback() incorrectly states a
"100ms" response while the actual polling interval is 500ms; update the comment
to reflect the real interval (500ms) or change the polling interval constant to
100ms if you intended faster response. Locate the polling loop that calls
invokeVolumeListenerCallback() and either (A) edit the comment text to say
"500ms" (or reference the polling constant name used, e.g., pollingIntervalMs /
VOLUME_POLL_INTERVAL_MS) or (B) change the poll interval constant to 100ms so
the comment matches; ensure the comment and the value referenced by the polling
loop remain consistent.

Comment on lines +33 to 44
fun update(volumes: List<Volume>) {
// 找出实际变化的项,避免整体刷新
val changedPositions = mutableListOf<Int>()
for (i in volumes.indices) {
if (i >= mVolumeList.size || volumes[i].value != mVolumeList[i].value
|| volumes[i].locked != mVolumeList[i].locked) {
changedPositions.add(i)
}
}
mVolumeList = volumes
changedPositions.forEach { notifyItemChanged(it) }
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle list size changes before partial item notifications.

Line 43 only dispatches notifyItemChanged, so insertions/removals are never announced. If volumes.size changes, RecyclerView can get out of sync with adapter updates.

Suggested fix
 `@MainThread`
 fun update(volumes: List<Volume>) {
+    if (volumes.size != mVolumeList.size) {
+        mVolumeList = volumes
+        notifyDataSetChanged()
+        return
+    }
+
     // 找出实际变化的项,避免整体刷新
     val changedPositions = mutableListOf<Int>()
     for (i in volumes.indices) {
-        if (i >= mVolumeList.size || volumes[i].value != mVolumeList[i].value
-            || volumes[i].locked != mVolumeList[i].locked) {
+        if (volumes[i].value != mVolumeList[i].value
+            || volumes[i].locked != mVolumeList[i].locked) {
             changedPositions.add(i)
         }
     }
     mVolumeList = volumes
     changedPositions.forEach { notifyItemChanged(it) }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 33 -
44, In update(volumes: List<Volume>) capture the old size (mVolumeList.size)
before assigning mVolumeList = volumes, then handle size changes first: if
volumes.size > oldSize call notifyItemRangeInserted(oldSize, volumes.size -
oldSize) (or notifyItemInserted per index) for new items; if volumes.size <
oldSize call notifyItemRangeRemoved(volumes.size, oldSize - volumes.size) (or
notifyItemRemoved per index) for removed items; after handling inserts/removes
compute changed positions only over the overlapping range (0 until min(oldSize,
volumes.size)) comparing volumes[i].value/locked with mVolumeList[i] and call
notifyItemChanged(i) for those indices; finally assign mVolumeList = volumes.
This ensures insertions/removals are announced before per-item change
notifications.

Comment on lines +97 to 100
if (mService?.getLocks()?.containsKey(volume.stream) == true) {
holder.binding.switchButton.isChecked = true
holder.binding.slider.isEnabled = false
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset unlocked UI state during bind to avoid stale rows.

Line 97 only applies state for locked rows. Unlocked rows are not explicitly reset, so recycled holders can keep old checked/disabled state.

Suggested fix
 private fun loadLockFromService(holder: ViewHolder, volume: Volume) {
-    if (mService?.getLocks()?.containsKey(volume.stream) == true) {
-        holder.binding.switchButton.isChecked = true
-        holder.binding.slider.isEnabled = false
-    }
+    val isLocked = mService?.getLocks()?.containsKey(volume.stream) == true
+    holder.binding.switchButton.isChecked = isLocked
+    holder.binding.slider.isEnabled = !isLocked
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (mService?.getLocks()?.containsKey(volume.stream) == true) {
holder.binding.switchButton.isChecked = true
holder.binding.slider.isEnabled = false
}
private fun loadLockFromService(holder: ViewHolder, volume: Volume) {
val isLocked = mService?.getLocks()?.containsKey(volume.stream) == true
holder.binding.switchButton.isChecked = isLocked
holder.binding.slider.isEnabled = !isLocked
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 97 -
100, In onBindViewHolder (VolumeAdapter.kt) the code only sets UI for locked
rows, causing recycled holders to keep prior checked/disabled states; add an
explicit else branch when checking
mService?.getLocks()?.containsKey(volume.stream) to reset the UI for unlocked
rows by setting holder.binding.switchButton.isChecked = false and
holder.binding.slider.isEnabled = true (reference the existing
holder.binding.switchButton and holder.binding.slider to locate where to add the
else).

Comment thread CODE_OF_CONDUCT.md
Comment on lines +57 to +60
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at the
repository owner's contact information. All complaints will be reviewed and
investigated promptly and fairly.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Enforcement contact is still a placeholder.

Line 59 should include a real contact method (email or issue/discussion route). The current placeholder leaves reporters without an actionable path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CODE_OF_CONDUCT.md` around lines 57 - 60, The enforcement contact is still a
placeholder in the sentence starting "Instances of abusive, harassing, or
otherwise unacceptable behavior may be reported to the community leaders
responsible for enforcement at the repository owner's contact information.";
replace that placeholder with a concrete contact method (for example a
maintainer/security email like security@your-org.com or a link to the repo's
issues/discussions route or a pointer to SECURITY.md) so reporters have an
actionable channel; update the same sentence in CODE_OF_CONDUCT.md to include
that real email or specific issue/discussion route and optionally mention
expected response/timeframe.

Comment thread CONTRIBUTING.md
```

Use one of these branch prefixes:
| Prefix | Purpose |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve markdownlint warnings in this doc block.

Current content triggers MD058 (table spacing) and MD040 (fenced code blocks missing language). Please add blank lines around the table and specify code fence languages (e.g., text where needed) to keep docs lint-clean.

Also applies to: 73-73, 98-98, 109-109, 149-149

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 41-41: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CONTRIBUTING.md` at line 41, Add blank lines before and after the Markdown
table that starts with "| Prefix | Purpose |" so it doesn't trigger MD058, and
annotate any fenced code blocks mentioned at lines 73, 98, 109, and 149 with an
explicit language (e.g., use ```text or ```bash as appropriate) to resolve
MD040; update the CONTRIBUTING.md by locating the table header string "| Prefix
| Purpose |" and the fenced code blocks at the referenced locations and insert a
blank line above and below the table and add the language specifier to each
fence.

Comment thread CONTRIBUTING.md
3. Add your translation credit to the [Translators](README.md#translators) section of README
4. Submit as `i18n(<locale>): add/update <language> translations`

**Locale codes**: Use [BCP 47](https://tools.ietf.org/html/bcp47) format (e.g., `zh`, `fr`, `ru-rRU`).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Locale example is malformed.

Line 143 uses ru-rRU, which is not a valid BCP 47 tag format. Use ru-RU (or explicitly document Android resource qualifier format separately).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CONTRIBUTING.md` at line 143, Update the malformed BCP 47 example under the
"Locale codes" section by replacing the incorrect tag `ru-rRU` with the correct
`ru-RU`; ensure the example list (`zh`, `fr`, `ru-RU`) follows BCP 47
casing/format rules (or add a separate note if you intend to document Android
resource qualifier format separately).

VolumeLockr Chinese Translator added 2 commits May 24, 2026 11:45
新增功能:
- 定时音量锁定:按时间段自动锁定音量,多时段、工作日/周末、每音频流独立目标
- 手动临时解锁:定时锁定期间可手动解除 15/30/60/120 分钟
- 调度配置 JSON 导入/导出:备份与恢复定时计划
- ScheduleData/ScheduleManager/ScheduleFragment/ScheduleAdapter 完整调度子系统

修复:
- 定时调度完全不生效(VolumeService 仅检查手动锁定项决定是否停止服务)
- 移除所有手动锁后调度轮询意外停止(VolumeAdapter.adjustService bug)
- 开机后定时调度未自动启动(BootReceiver 未检查调度配置)
- JSON 导入的 CUSTOM day type 编辑后类型丢失为 WEEKEND

项目规范化:
- security-crypto 1.1.0-alpha06 -> 1.1.0 stable
- CI actions v3 -> v4,Detekt toolVersion 同步 1.23.1 -> 1.23.8
- JVM target 1.8 -> 17,移除废弃 buildConfig flag
- 新增 .editorconfig、SECURITY.md
- 修复 PR 模板格式(YAML issue form -> Markdown 标准模板)
- CONTRIBUTING/CHANGELOG 链接更新至 xunnv 仓库
- 清理未使用的 res/menu/options.xml
- README 中英文版同步新增调度功能介绍
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.github/workflows/android.yml (2)

15-21: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin GitHub Actions to commit SHAs and disable checkout credential persistence

android.yml uses movable actions/checkout@v4 and actions/setup-java@v4 tags, and the checkout step does not set persist-credentials: false (no explicit workflow permissions: block either).

Suggested hardening
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@<pinned_checkout_sha>
+      with:
+        persist-credentials: false
@@
-    - name: set up JDK 17
-      uses: actions/setup-java@v4
+    - name: set up JDK 17
+      uses: actions/setup-java@<pinned_setup_java_sha>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/android.yml around lines 15 - 21, The workflow uses
movable tags for actions and leaves checkout credentials persistent; update the
actions references (actions/checkout and actions/setup-java) to use their commit
SHAs instead of `@v4`, add persist-credentials: false to the actions/checkout
step, and add a minimal repository-level permissions block to restrict
unnecessary write scopes; locate the checkout step (uses: actions/checkout) and
change it to include persist-credentials: false, and locate the setup-java step
(uses: actions/setup-java) and pin it to the corresponding commit SHA.

9-13: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Declare least-privilege workflow permissions explicitly.

.github/workflows/android.yml job build has no permissions: block, so it relies on GitHub defaults. Add a least-privilege permissions block (e.g., for actions/checkout):

Suggested hardening
 jobs:
   build:
+    permissions:
+      contents: read
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/android.yml around lines 9 - 13, Add an explicit
least-privilege permissions block to the GitHub Actions workflow for the "build"
job: declare the job-level permissions (e.g., set contents: read to allow
actions/checkout and any other minimal required scopes) instead of relying on
repository defaults; update the "build" job definition in
.github/workflows/android.yml to include this permissions block so the workflow
runs with only the necessary privileges.
.github/ISSUE_TEMPLATE/bug_report.yml (1)

80-80: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update logcat filter tag in bug template (Line 80).

The command filters on VolumeService, but current logging uses VolumeLockr; reporters may miss key boot/schedule logs.

Proposed fix
-        Run `adb logcat -s VolumeService:V -d > log.txt` and attach the file here.
+        Run `adb logcat -s VolumeLockr:V -d > log.txt` and attach the file here.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/ISSUE_TEMPLATE/bug_report.yml at line 80, Update the logcat command
tag from "VolumeService" to "VolumeLockr" in the bug report template so
reporters capture current logs; locate the string "adb logcat -s VolumeService:V
-d > log.txt" and replace "VolumeService" with "VolumeLockr", and also search
the template for any other occurrences of "VolumeService" to update them
consistently.
♻️ Duplicate comments (3)
app/src/main/java/com/klee/volumelockr/service/VolumeService.kt (1)

173-174: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale 100ms comments to match 500ms polling.

Line 173 and Line 350 still document 100ms while POLL_INTERVAL_MS is 500ms, which is misleading during maintenance/debugging. This was already reported earlier and is still valid.

Also applies to: 350-351

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/service/VolumeService.kt` around lines
173 - 174, The javadoc comments in VolumeService that describe the polling
interval are stale (they say 100ms) while the actual constant POLL_INTERVAL_MS
is 500ms; update the comments near the method that starts polling (the doc for
startPolling/initialDelayMs) and the other occurrence around line ~350 to
reflect 500ms so documentation matches the POLL_INTERVAL_MS constant and avoids
confusion during maintenance.
app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt (2)

36-47: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle insert/remove notifications before partial updates.

Line 40 and Line 46 still only emit notifyItemChanged, so list size changes can desync adapter state (append/remove paths). This was already reported earlier and is still valid.

Suggested fix
 `@MainThread`
 fun update(volumes: List<Volume>) {
-    // 找出实际变化的项,避免整体刷新
-    val changedPositions = mutableListOf<Int>()
-    for (i in volumes.indices) {
-        if (i >= mVolumeList.size || volumes[i].value != mVolumeList[i].value
-            || volumes[i].locked != mVolumeList[i].locked) {
-            changedPositions.add(i)
-        }
-    }
-    mVolumeList = volumes
-    changedPositions.forEach { notifyItemChanged(it) }
+    val oldList = mVolumeList
+    val oldSize = oldList.size
+    val newSize = volumes.size
+    val overlap = minOf(oldSize, newSize)
+
+    mVolumeList = volumes
+
+    if (newSize > oldSize) {
+        notifyItemRangeInserted(oldSize, newSize - oldSize)
+    } else if (newSize < oldSize) {
+        notifyItemRangeRemoved(newSize, oldSize - newSize)
+    }
+
+    for (i in 0 until overlap) {
+        if (volumes[i].value != oldList[i].value || volumes[i].locked != oldList[i].locked) {
+            notifyItemChanged(i)
+        }
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 36 -
47, In update(volumes: List<Volume>) the adapter only calls notifyItemChanged
for differing indices which breaks when list size changes; compute diffs against
the old list (store old = mVolumeList), detect inserts and removes (e.g., if
volumes.size > old.size call notifyItemRangeInserted for the new tail or call
notifyItemRangeRemoved when smaller, or emit per-index
notifyItemInserted/notifyItemRemoved if not only tail changes) and apply those
notifications before calling notifyItemChanged for positions in
changedPositions, then assign mVolumeList = volumes; reference update,
mVolumeList, changedPositions, and
notifyItemChanged/notifyItemInserted/notifyItemRemoved/notifyItemRangeInserted/notifyItemRangeRemoved
when making the change.

99-104: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset unlocked row state explicitly during bind.

Line 100-103 only applies locked UI state; unlocked rows are not reset, so recycled holders can show stale checked/disabled controls. This was already reported earlier and is still valid.

Suggested fix
 private fun loadLockFromService(holder: ViewHolder, volume: Volume) {
-    if (mService?.getLocks()?.containsKey(volume.stream) == true) {
-        holder.binding.switchButton.isChecked = true
-        holder.binding.slider.isEnabled = false
-    }
+    val isLocked = mService?.getLocks()?.containsKey(volume.stream) == true
+    holder.binding.switchButton.isChecked = isLocked
+    holder.binding.slider.isEnabled = !isLocked
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 99 -
104, The loadLockFromService method only sets the locked UI state and leaves
unlocked rows unchanged, causing recycled ViewHolders to display stale controls;
update loadLockFromService to explicitly handle both cases: if
mService?.getLocks()?.containsKey(volume.stream) == true then set
holder.binding.switchButton.isChecked = true and holder.binding.slider.isEnabled
= false, else set holder.binding.switchButton.isChecked = false and
holder.binding.slider.isEnabled = true (handle mService null-safely as needed)
so every bind resets the row state.
🧹 Nitpick comments (1)
app/src/main/res/layout/item_schedule.xml (1)

58-62: ⚡ Quick win

Add an explicit accessibility label for the switch.

switchSlotEnabled may be announced without context in TalkBack. Please set a per-item contentDescription (e.g., slot name + enabled state) when binding in the adapter so users can identify which schedule they’re toggling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/res/layout/item_schedule.xml` around lines 58 - 62, The switch
with id switchSlotEnabled lacks an accessibility label; update the adapter's
binding logic (e.g., in the ViewHolder.bind or onBindViewHolder of your
ScheduleAdapter) to set a per-item contentDescription that combines the slot
name and its enabled state (for example: "<slotName>, enabled" or "<slotName>,
disabled") whenever you set the switch's checked value so TalkBack announces
meaningful context for each switch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/pull_request_template.md:
- Around line 23-27: The fenced code block in .github/pull_request_template.md
is missing a language and triggers markdownlint MD040; update the
triple-backtick opening fence to include a language (e.g., change ``` to
```text) for the block that contains the three checklist lines so the code fence
becomes a language-specified block and satisfies the linter.

In `@app/src/main/java/com/klee/volumelockr/schedule/ScheduleData.kt`:
- Around line 35-52: The matchesDay + matchesTime split fails for cross-midnight
windows because matchesTime after-midnight should be validated against the
previous-day rules; update logic so that when crossesMidnight is true and
nowMinutes < endMinutes you call matchesDay(previousDay) (compute previousDay =
(dayOfWeek - 1 mod 7) consistent with your day numbering) instead of
matchesDay(currentDay), while keeping existing behavior for nowMinutes >=
startMinutes; modify the caller or add a helper (or a new combined method) that
uses matchesTime(hour, minute) together with dayType/customDays and this
previous-day rule so CUSTOM/WEEKEND/WORKDAY are evaluated correctly for the
after-midnight portion (refer to matchesDay, matchesTime, crossesMidnight,
startMinutes, endMinutes, customDays).

In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleAdapter.kt`:
- Around line 55-60: The getDayTypeLabel function currently maps DayType.CUSTOM
to the everyday label; update getDayTypeLabel (in ScheduleAdapter) so
DayType.CUSTOM returns a distinct localized string (e.g.,
context.getString(com.klee.volumelockr.R.string.schedule_custom)) instead of
schedule_everyday, and add the corresponding schedule_custom entry to your
strings.xml resources with the proper translation(s).
- Around line 44-46: The binding sequence is causing recycled Switch listeners
to fire during bind; in onBindViewHolder (where holder.binding.switchSlotEnabled
is used) clear the old listener first by calling
holder.binding.switchSlotEnabled.setOnCheckedChangeListener(null), then set
holder.binding.switchSlotEnabled.isChecked = slot.enabled, and finally reattach
the new listener via setOnCheckedChangeListener { _, checked -> ... } so the
recycled callbacks (e.g., onToggle) are not triggered during view binding.

In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleFragment.kt`:
- Around line 110-111: In ScheduleFragment update the calculation of remaining
minutes so it can never be negative: compute the delta as
(config.temporaryUnlockUntil - System.currentTimeMillis()) / 60000 and clamp it
to zero (e.g., using Math.max or Kotlin's coerceAtLeast) before passing it to
getString; update the reference where binding.tvTempUnlockText.text is set to
use the non-negative remaining value.
- Line 401: The import path in ScheduleFragment's code uses
requireContext().contentResolver.openInputStream(uri)?.bufferedReader()?.readText()
without closing the stream; update the import logic to scope-close the
InputStream and the BufferedReader by using Kotlin's use blocks (e.g.,
openInputStream(uri)?.use { input -> input.bufferedReader().use { reader ->
reader.readText() } }) so the file descriptor is always released, and assign
that result to the existing json variable.

In `@app/src/main/res/values/strings.xml`:
- Line 66: Update the string resource named schedule_lock_active_msg to remove
the extra spaces around the %s placeholder (and ensure any surrounding quotation
marks are adjacent to %s rather than separated by spaces) so the formatted app
name renders without awkward gaps; locate schedule_lock_active_msg in the
strings.xml and adjust the text accordingly.

In `@CHANGELOG.md`:
- Around line 104-105: Remove or repurpose the unused markdown link reference
"[1.7.1-zh]" in the CHANGELOG: either delete the `[1.7.1-zh]:
https://github.com/xunnv/VolumeLockr/compare/v1.7.1...v1.7.1-zh` reference or
add a corresponding changelog section that references `[1.7.1-zh]` so the label
is actually used and markdownlint will no longer flag it.

---

Outside diff comments:
In @.github/ISSUE_TEMPLATE/bug_report.yml:
- Line 80: Update the logcat command tag from "VolumeService" to "VolumeLockr"
in the bug report template so reporters capture current logs; locate the string
"adb logcat -s VolumeService:V -d > log.txt" and replace "VolumeService" with
"VolumeLockr", and also search the template for any other occurrences of
"VolumeService" to update them consistently.

In @.github/workflows/android.yml:
- Around line 15-21: The workflow uses movable tags for actions and leaves
checkout credentials persistent; update the actions references (actions/checkout
and actions/setup-java) to use their commit SHAs instead of `@v4`, add
persist-credentials: false to the actions/checkout step, and add a minimal
repository-level permissions block to restrict unnecessary write scopes; locate
the checkout step (uses: actions/checkout) and change it to include
persist-credentials: false, and locate the setup-java step (uses:
actions/setup-java) and pin it to the corresponding commit SHA.
- Around line 9-13: Add an explicit least-privilege permissions block to the
GitHub Actions workflow for the "build" job: declare the job-level permissions
(e.g., set contents: read to allow actions/checkout and any other minimal
required scopes) instead of relying on repository defaults; update the "build"
job definition in .github/workflows/android.yml to include this permissions
block so the workflow runs with only the necessary privileges.

---

Duplicate comments:
In `@app/src/main/java/com/klee/volumelockr/service/VolumeService.kt`:
- Around line 173-174: The javadoc comments in VolumeService that describe the
polling interval are stale (they say 100ms) while the actual constant
POLL_INTERVAL_MS is 500ms; update the comments near the method that starts
polling (the doc for startPolling/initialDelayMs) and the other occurrence
around line ~350 to reflect 500ms so documentation matches the POLL_INTERVAL_MS
constant and avoids confusion during maintenance.

In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt`:
- Around line 36-47: In update(volumes: List<Volume>) the adapter only calls
notifyItemChanged for differing indices which breaks when list size changes;
compute diffs against the old list (store old = mVolumeList), detect inserts and
removes (e.g., if volumes.size > old.size call notifyItemRangeInserted for the
new tail or call notifyItemRangeRemoved when smaller, or emit per-index
notifyItemInserted/notifyItemRemoved if not only tail changes) and apply those
notifications before calling notifyItemChanged for positions in
changedPositions, then assign mVolumeList = volumes; reference update,
mVolumeList, changedPositions, and
notifyItemChanged/notifyItemInserted/notifyItemRemoved/notifyItemRangeInserted/notifyItemRangeRemoved
when making the change.
- Around line 99-104: The loadLockFromService method only sets the locked UI
state and leaves unlocked rows unchanged, causing recycled ViewHolders to
display stale controls; update loadLockFromService to explicitly handle both
cases: if mService?.getLocks()?.containsKey(volume.stream) == true then set
holder.binding.switchButton.isChecked = true and holder.binding.slider.isEnabled
= false, else set holder.binding.switchButton.isChecked = false and
holder.binding.slider.isEnabled = true (handle mService null-safely as needed)
so every bind resets the row state.

---

Nitpick comments:
In `@app/src/main/res/layout/item_schedule.xml`:
- Around line 58-62: The switch with id switchSlotEnabled lacks an accessibility
label; update the adapter's binding logic (e.g., in the ViewHolder.bind or
onBindViewHolder of your ScheduleAdapter) to set a per-item contentDescription
that combines the slot name and its enabled state (for example: "<slotName>,
enabled" or "<slotName>, disabled") whenever you set the switch's checked value
so TalkBack announces meaningful context for each switch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 75d3ff6a-74ed-421e-b0df-9bf0e02f6311

📥 Commits

Reviewing files that changed from the base of the PR and between e4499c3 and 39da935.

📒 Files selected for processing (30)
  • .editorconfig
  • .github/FUNDING.yml
  • .github/ISSUE_TEMPLATE/bug_report.yml
  • .github/pull_request_template.md
  • .github/workflows/android.yml
  • CHANGELOG.md
  • CONTRIBUTING.md
  • README.md
  • SECURITY.md
  • app/build.gradle
  • app/src/main/java/com/klee/volumelockr/BootReceiver.kt
  • app/src/main/java/com/klee/volumelockr/schedule/ScheduleData.kt
  • app/src/main/java/com/klee/volumelockr/schedule/ScheduleManager.kt
  • app/src/main/java/com/klee/volumelockr/service/VolumeService.kt
  • app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt
  • app/src/main/java/com/klee/volumelockr/ui/ScheduleAdapter.kt
  • app/src/main/java/com/klee/volumelockr/ui/ScheduleFragment.kt
  • app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt
  • app/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.kt
  • app/src/main/res/layout/dialog_time_slot.xml
  • app/src/main/res/layout/fragment_schedule.xml
  • app/src/main/res/layout/item_schedule.xml
  • app/src/main/res/menu/bottom_nav_menu.xml
  • app/src/main/res/menu/options.xml
  • app/src/main/res/navigation/navigation_graph.xml
  • app/src/main/res/values-zh/strings.xml
  • app/src/main/res/values/strings.xml
  • build.gradle
  • gradle.properties
  • gradle/libs.versions.toml
💤 Files with no reviewable changes (1)
  • app/src/main/res/menu/options.xml
✅ Files skipped from review due to trivial changes (8)
  • .editorconfig
  • gradle/libs.versions.toml
  • .github/FUNDING.yml
  • app/src/main/res/layout/dialog_time_slot.xml
  • SECURITY.md
  • app/src/main/res/layout/fragment_schedule.xml
  • CONTRIBUTING.md
  • README.md

Comment on lines +23 to +27
```
- Tested on [Device] (Android [Version])
- ./gradlew detekt passes
- ./gradlew test passes
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Specify a language for the fenced code block to satisfy markdownlint.

This currently triggers MD040.

Suggested fix
-```
+```text
 - Tested on [Device] (Android [Version])
 - ./gradlew detekt passes
 - ./gradlew test passes
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 23-23: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/pull_request_template.md around lines 23 - 27, The fenced code block
in .github/pull_request_template.md is missing a language and triggers
markdownlint MD040; update the triple-backtick opening fence to include a
language (e.g., change ``` to ```text) for the block that contains the three
checklist lines so the code fence becomes a language-specified block and
satisfies the linter.

Comment on lines +35 to +52
/** 检查给定的星期是否匹配此时间段的星期规则 */
fun matchesDay(dayOfWeek: Int /* 1=周日 Calendar.SUNDAY → 需转换为 0=周日 */): Boolean {
return when (dayType) {
DayType.EVERYDAY -> true
DayType.WORKDAY -> dayOfWeek in setOf(1, 2, 3, 4, 5) // 周一~周五 (Calendar: 2-6)
DayType.WEEKEND -> dayOfWeek in setOf(0, 6) // 周六日 (转换后)
DayType.CUSTOM -> dayOfWeek in customDays
}
}

/** 检查给定的时分是否落在此时间段内 */
fun matchesTime(hour: Int, minute: Int): Boolean {
val nowMinutes = hour * 60 + minute
return if (crossesMidnight) {
nowMinutes >= startMinutes || nowMinutes < endMinutes
} else {
nowMinutes in startMinutes until endMinutes
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cross-midnight slots can miss the after-midnight portion for day-restricted rules.

Current logic checks matchesDay(currentDay) and matchesTime(...) separately. For cross-midnight windows, times after midnight should often evaluate against the previous day’s rule set, otherwise CUSTOM/WEEKEND cases can be skipped incorrectly.

Suggested direction
+fun matchesDateTime(dayOfWeek: Int, hour: Int, minute: Int): Boolean {
+    val nowMinutes = hour * 60 + minute
+    if (!crossesMidnight) return matchesDay(dayOfWeek) && nowMinutes in startMinutes until endMinutes
+
+    return if (nowMinutes >= startMinutes) {
+        matchesDay(dayOfWeek)
+    } else if (nowMinutes < endMinutes) {
+        val prevDay = (dayOfWeek + 6) % 7
+        matchesDay(prevDay)
+    } else {
+        false
+    }
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/schedule/ScheduleData.kt` around lines
35 - 52, The matchesDay + matchesTime split fails for cross-midnight windows
because matchesTime after-midnight should be validated against the previous-day
rules; update logic so that when crossesMidnight is true and nowMinutes <
endMinutes you call matchesDay(previousDay) (compute previousDay = (dayOfWeek -
1 mod 7) consistent with your day numbering) instead of matchesDay(currentDay),
while keeping existing behavior for nowMinutes >= startMinutes; modify the
caller or add a helper (or a new combined method) that uses matchesTime(hour,
minute) together with dayType/customDays and this previous-day rule so
CUSTOM/WEEKEND/WORKDAY are evaluated correctly for the after-midnight portion
(refer to matchesDay, matchesTime, crossesMidnight, startMinutes, endMinutes,
customDays).

Comment on lines +44 to +46
holder.binding.switchSlotEnabled.isChecked = slot.enabled
holder.binding.switchSlotEnabled.setOnCheckedChangeListener(null)
holder.binding.switchSlotEnabled.setOnCheckedChangeListener { _, checked ->
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent recycled listener callbacks during bind (Line 44).

isChecked is assigned before clearing the old listener, so recycled holders can emit unintended onToggle callbacks while binding.

Proposed fix
-        holder.binding.switchSlotEnabled.isChecked = slot.enabled
-        holder.binding.switchSlotEnabled.setOnCheckedChangeListener(null)
+        holder.binding.switchSlotEnabled.setOnCheckedChangeListener(null)
+        holder.binding.switchSlotEnabled.isChecked = slot.enabled
         holder.binding.switchSlotEnabled.setOnCheckedChangeListener { _, checked ->
             onToggle(slot, checked)
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleAdapter.kt` around lines 44
- 46, The binding sequence is causing recycled Switch listeners to fire during
bind; in onBindViewHolder (where holder.binding.switchSlotEnabled is used) clear
the old listener first by calling
holder.binding.switchSlotEnabled.setOnCheckedChangeListener(null), then set
holder.binding.switchSlotEnabled.isChecked = slot.enabled, and finally reattach
the new listener via setOnCheckedChangeListener { _, checked -> ... } so the
recycled callbacks (e.g., onToggle) are not triggered during view binding.

Comment on lines +55 to +60
private fun getDayTypeLabel(dayType: DayType): String = when (dayType) {
DayType.EVERYDAY -> context.getString(com.klee.volumelockr.R.string.schedule_everyday)
DayType.WORKDAY -> context.getString(com.klee.volumelockr.R.string.schedule_workday)
DayType.WEEKEND -> context.getString(com.klee.volumelockr.R.string.schedule_weekend)
DayType.CUSTOM -> context.getString(com.klee.volumelockr.R.string.schedule_everyday)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Show a distinct label for DayType.CUSTOM (Line 59).

Mapping CUSTOM to “Every day” misrepresents schedule scope in the list UI. Use a dedicated localized label for custom days.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleAdapter.kt` around lines 55
- 60, The getDayTypeLabel function currently maps DayType.CUSTOM to the everyday
label; update getDayTypeLabel (in ScheduleAdapter) so DayType.CUSTOM returns a
distinct localized string (e.g.,
context.getString(com.klee.volumelockr.R.string.schedule_custom)) instead of
schedule_everyday, and add the corresponding schedule_custom entry to your
strings.xml resources with the proper translation(s).

Comment on lines +110 to +111
val remaining = (config.temporaryUnlockUntil - System.currentTimeMillis()) / 60000
binding.tvTempUnlockText.text = getString(R.string.schedule_temp_unlock_remaining, remaining)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clamp temporary unlock minutes to non-negative (Line 110).

If expiration just passed, UI can show negative minutes.

Proposed fix
-            val remaining = (config.temporaryUnlockUntil - System.currentTimeMillis()) / 60000
+            val remaining = ((config.temporaryUnlockUntil - System.currentTimeMillis()) / 60000).coerceAtLeast(0)
             binding.tvTempUnlockText.text = getString(R.string.schedule_temp_unlock_remaining, remaining)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleFragment.kt` around lines
110 - 111, In ScheduleFragment update the calculation of remaining minutes so it
can never be negative: compute the delta as (config.temporaryUnlockUntil -
System.currentTimeMillis()) / 60000 and clamp it to zero (e.g., using Math.max
or Kotlin's coerceAtLeast) before passing it to getString; update the reference
where binding.tvTempUnlockText.text is set to use the non-negative remaining
value.

private fun handleImport(uriString: String) {
try {
val uri = android.net.Uri.parse(uriString)
val json = requireContext().contentResolver.openInputStream(uri)?.bufferedReader()?.readText()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Close import input stream with use (Line 401).

The current import read path does not scope-close the stream, which can leak file descriptors on repeated imports.

Proposed fix
-            val json = requireContext().contentResolver.openInputStream(uri)?.bufferedReader()?.readText()
+            val json = requireContext().contentResolver
+                .openInputStream(uri)
+                ?.bufferedReader()
+                ?.use { it.readText() }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val json = requireContext().contentResolver.openInputStream(uri)?.bufferedReader()?.readText()
val json = requireContext().contentResolver
.openInputStream(uri)
?.bufferedReader()
?.use { it.readText() }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/ScheduleFragment.kt` at line 401,
The import path in ScheduleFragment's code uses
requireContext().contentResolver.openInputStream(uri)?.bufferedReader()?.readText()
without closing the stream; update the import logic to scope-close the
InputStream and the BufferedReader by using Kotlin's use blocks (e.g.,
openInputStream(uri)?.use { input -> input.bufferedReader().use { reader ->
reader.readText() } }) so the file descriptor is always released, and assign
that result to the existing json variable.

<string name="schedule_import_success">Schedules imported</string>
<string name="schedule_import_failed">Import failed</string>
<string name="delete">Delete</string>
<string name="schedule_lock_active_msg">" %s " is currently active. Temporarily unlock to adjust volume?</string>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove unintended spaces around %s in active-lock message (Line 66).

Current text renders awkward spacing/quotes (" %s "). Use %s directly.

Proposed fix
-    <string name="schedule_lock_active_msg">" %s " is currently active. Temporarily unlock to adjust volume?</string>
+    <string name="schedule_lock_active_msg">"%s" is currently active. Temporarily unlock to adjust volume?</string>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<string name="schedule_lock_active_msg">" %s " is currently active. Temporarily unlock to adjust volume?</string>
<string name="schedule_lock_active_msg">"%s" is currently active. Temporarily unlock to adjust volume?</string>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/res/values/strings.xml` at line 66, Update the string resource
named schedule_lock_active_msg to remove the extra spaces around the %s
placeholder (and ensure any surrounding quotation marks are adjacent to %s
rather than separated by spaces) so the formatted app name renders without
awkward gaps; locate schedule_lock_active_msg in the strings.xml and adjust the
text accordingly.

Comment thread CHANGELOG.md
Comment on lines +104 to +105
[Unreleased]: https://github.com/xunnv/VolumeLockr/compare/v1.7.1-zh...HEAD
[1.7.1-zh]: https://github.com/xunnv/VolumeLockr/compare/v1.7.1...v1.7.1-zh
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove or use the unused 1.7.1-zh link reference.

Line 105 defines [1.7.1-zh] but no changelog section references it, so it becomes stale noise and triggers markdown lint.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 105-105: Link and image reference definitions should be needed
Unused link or image reference definition: "1.7.1-zh"

(MD053, link-image-reference-definitions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 104 - 105, Remove or repurpose the unused markdown
link reference "[1.7.1-zh]" in the CHANGELOG: either delete the `[1.7.1-zh]:
https://github.com/xunnv/VolumeLockr/compare/v1.7.1...v1.7.1-zh` reference or
add a corresponding changelog section that references `[1.7.1-zh]` so the label
is actually used and markdownlint will no longer flag it.

@jonathanklee
Copy link
Copy Markdown
Owner

@xunnv Comments in the code and commits description should be in english.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt (2)

103-111: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Always reset row UI for both locked and unlocked states during bind.

Only handling the locked case allows recycled holders to retain stale checked/disabled state for unlocked rows.

✅ Suggested bind-safe state handling
 private fun loadLockFromService(holder: ViewHolder, volume: Volume) {
-    if (mService?.getLocks()?.containsKey(volume.stream) == true) {
-        // 临时移除监听器,避免设置 isChecked 时触发 onVolumeLocked
-        holder.binding.switchButton.setOnCheckedChangeListener(null)
-        holder.binding.switchButton.isChecked = true
-        holder.binding.slider.isEnabled = false
-        // 恢复监听器
-        registerSwitchButtonCallback(holder, volume)
-    }
+    val isLocked = mService?.getLocks()?.containsKey(volume.stream) == true
+    holder.binding.switchButton.setOnCheckedChangeListener(null)
+    holder.binding.switchButton.isChecked = isLocked
+    holder.binding.slider.isEnabled = !isLocked
+    registerSwitchButtonCallback(holder, volume)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 103
- 111, The bind currently only updates UI when a volume is locked causing
recycled ViewHolders to retain stale checked/disabled state; in
loadLockFromService add an explicit else branch that also resets the row UI for
the unlocked case: detach the switch listener
(holder.binding.switchButton.setOnCheckedChangeListener(null)), set
holder.binding.switchButton.isChecked = false, set
holder.binding.slider.isEnabled = true, then reattach the callback via
registerSwitchButtonCallback(holder, volume); ensure you always remove the
listener before changing isChecked for both branches and use the same
registerSwitchButtonCallback(...) to restore the listener.

39-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle insertions/removals before partial item-change notifications.

When volumes.size changes, notifyItemChanged alone is not sufficient and can put RecyclerView in an inconsistent state.

✅ Suggested fix for stable RecyclerView updates
 `@MainThread`
 fun update(volumes: List<Volume>) {
+    val oldSize = mVolumeList.size
+    val overlap = minOf(oldSize, volumes.size)
+
     // 找出实际变化的项,避免整体刷新
     val changedPositions = mutableListOf<Int>()
-    for (i in volumes.indices) {
-        if (i >= mVolumeList.size || volumes[i].value != mVolumeList[i].value
+    for (i in 0 until overlap) {
+        if (volumes[i].value != mVolumeList[i].value
             || volumes[i].locked != mVolumeList[i].locked) {
             changedPositions.add(i)
         }
     }
+
     mVolumeList = volumes
+    when {
+        volumes.size > oldSize -> notifyItemRangeInserted(oldSize, volumes.size - oldSize)
+        volumes.size < oldSize -> notifyItemRangeRemoved(volumes.size, oldSize - volumes.size)
+    }
     changedPositions.forEach { notifyItemChanged(it) }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 39 -
50, The update method currently only calls notifyItemChanged and ignores
list-size changes; capture the old list (val oldList = mVolumeList), compute
oldSize and newSize, update mVolumeList = volumes, then handle
removals/insertions before per-item changes: if newSize < oldSize call
notifyItemRangeRemoved(newSize, oldSize - newSize); if newSize > oldSize call
notifyItemRangeInserted(oldSize, newSize - oldSize); finally iterate 0 until
min(oldSize, newSize) and call notifyItemChanged(i) only for items where
volumes[i].value or volumes[i].locked differ from oldList[i]; use the update
function name and
mVolumeList/notifyItemRangeRemoved/notifyItemRangeInserted/notifyItemChanged to
locate the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt`:
- Around line 194-199: The cancel handler set in setOnCancelListener currently
only clears mTempUnlockDialogShowing but doesn't revert the UI state, so when
the temp-unlock dialog is dismissed via back/outside the switch remains checked;
update the setOnCancelListener to also reset
holder.binding.switchButton.isChecked = false (the same action performed in the
setNegativeButton lambda) and ensure any cleanup done in onVolumeLocked()
remains consistent with this UI change so the switch accurately reflects that no
lock was added.

In `@app/src/main/res/layout/dialog_time_slot.xml`:
- Line 14: Replace the Chinese inline XML comments in dialog_time_slot.xml with
English equivalents to keep the codebase accessible; specifically update the
comment currently "启用开关 + 名称" and the other Chinese comments at the same
locations (lines showing the other UI comment blocks) to clear English phrases
(e.g., "Enable switch + label", "Start time", "End time", "Repeat options" —
adjust to match the actual UI elements) by editing the XML comment nodes so they
are in English; ensure the comments remain as XML comments (<!-- ... -->) and
preserve surrounding tags/IDs such as the time slot layout elements to avoid
changing structure.
- Line 113: The layout currently hardcodes time literals via
android:text="08:00" (and similarly "22:00"); replace those static literals with
string resources (e.g., `@string/default_start_time` and `@string/default_end_time`)
or remove the android:text and set the default times at runtime using
locale-aware formatting (e.g., DateFormat/TimeFormatter) in the containing
view/activity/fragment. Update dialog_time_slot.xml to reference the new string
resource keys and add corresponding entries to strings.xml, or clear the
android:text attributes and set the formatted time in the code path that
inflates the dialog (use the view IDs for the time TextViews to locate them).

---

Duplicate comments:
In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt`:
- Around line 103-111: The bind currently only updates UI when a volume is
locked causing recycled ViewHolders to retain stale checked/disabled state; in
loadLockFromService add an explicit else branch that also resets the row UI for
the unlocked case: detach the switch listener
(holder.binding.switchButton.setOnCheckedChangeListener(null)), set
holder.binding.switchButton.isChecked = false, set
holder.binding.slider.isEnabled = true, then reattach the callback via
registerSwitchButtonCallback(holder, volume); ensure you always remove the
listener before changing isChecked for both branches and use the same
registerSwitchButtonCallback(...) to restore the listener.
- Around line 39-50: The update method currently only calls notifyItemChanged
and ignores list-size changes; capture the old list (val oldList = mVolumeList),
compute oldSize and newSize, update mVolumeList = volumes, then handle
removals/insertions before per-item changes: if newSize < oldSize call
notifyItemRangeRemoved(newSize, oldSize - newSize); if newSize > oldSize call
notifyItemRangeInserted(oldSize, newSize - oldSize); finally iterate 0 until
min(oldSize, newSize) and call notifyItemChanged(i) only for items where
volumes[i].value or volumes[i].locked differ from oldList[i]; use the update
function name and
mVolumeList/notifyItemRangeRemoved/notifyItemRangeInserted/notifyItemChanged to
locate the code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5ee09874-c823-426b-b66a-a5b0fdb6edb0

📥 Commits

Reviewing files that changed from the base of the PR and between 39da935 and b54f7b3.

⛔ Files ignored due to path filters (3)
  • fastlane/metadata/android/en-US/images/icon.png is excluded by !**/*.png
  • fastlane/metadata/android/en-US/images/phoneScreenshots/1.png is excluded by !**/*.png
  • fastlane/metadata/android/en-US/images/phoneScreenshots/2.png is excluded by !**/*.png
📒 Files selected for processing (12)
  • .github/FUNDING.yml
  • app/src/androidTest/java/com/klee/volumelockr/ExampleInstrumentedTest.kt
  • app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt
  • app/src/main/res/layout/dialog_time_slot.xml
  • app/src/test/java/com/klee/volumelockr/ExampleUnitTest.kt
  • fastlane/metadata/android/en-US/full_description.txt
  • fastlane/metadata/android/en-US/short_description.txt
  • fastlane/metadata/android/en-US/title.txt
  • fastlane/metadata/android/fr-FR/full_description.txt
  • fastlane/metadata/android/fr-FR/short_description.txt
  • fastlane/metadata/android/ru/full_description.txt
  • fastlane/metadata/android/ru/short_description.txt
💤 Files with no reviewable changes (10)
  • fastlane/metadata/android/en-US/short_description.txt
  • fastlane/metadata/android/fr-FR/short_description.txt
  • fastlane/metadata/android/ru/full_description.txt
  • fastlane/metadata/android/en-US/full_description.txt
  • fastlane/metadata/android/fr-FR/full_description.txt
  • fastlane/metadata/android/ru/short_description.txt
  • fastlane/metadata/android/en-US/title.txt
  • app/src/androidTest/java/com/klee/volumelockr/ExampleInstrumentedTest.kt
  • .github/FUNDING.yml
  • app/src/test/java/com/klee/volumelockr/ExampleUnitTest.kt

Comment on lines +194 to +199
.setOnCancelListener { mTempUnlockDialogShowing = false }
.setNegativeButton(android.R.string.cancel) { _, _ ->
mTempUnlockDialogShowing = false
// 取消时恢复开关状态,避免开关处于开启但无锁定的矛盾状态
holder.binding.switchButton.isChecked = false
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
# locate file
ls -R

Repository: jonathanklee/VolumeLockr

Length of output: 2542


🏁 Script executed:

#!/bin/bash
set -euo pipefail
fd -a "VolumeAdapter.kt" . -t f

Repository: jonathanklee/VolumeLockr

Length of output: 146


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
echo "FILE=$FILE"
wc -l "$FILE"
rg -n "setOnCancelListener|switchButton|registerSwitchButtonCallback|setNegativeButton" "$FILE" || true

Repository: jonathanklee/VolumeLockr

Length of output: 1001


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
# print around the line range mentioned (194-199) and some context
nl -ba "$FILE" | sed -n '160,240p'

Repository: jonathanklee/VolumeLockr

Length of output: 111


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
echo "FILE=$FILE"
wc -l "$FILE"
echo "---- matches ----"
rg -n "setOnCancelListener|switchButton|registerSwitchButtonCallback|setNegativeButton" "$FILE" || true
echo "---- context 160-240 ----"
nl -ba "$FILE" | sed -n '160,240p'

Repository: jonathanklee/VolumeLockr

Length of output: 1106


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
echo "---- context 240-330 ----"
nl -ba "$FILE" | sed -n '240,330p'

Repository: jonathanklee/VolumeLockr

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
echo "---- context 130-200 (wider) ----"
nl -ba "$FILE" | sed -n '120,215p'

Repository: jonathanklee/VolumeLockr

Length of output: 163


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="$(fd -a "VolumeAdapter.kt" . -t f | head -n 1)"
echo "FILE=$FILE"
wc -l "$FILE"
echo "---- rg matches ----"
rg -n "setOnCancelListener|switchButton|registerSwitchButtonCallback|setNegativeButton" "$FILE" || true
echo "---- context 150-230 ----"
nl -ba "$FILE" | sed -n '150,230p'
echo "---- context 230-310 ----"
nl -ba "$FILE" | sed -n '230,310p'

Repository: jonathanklee/VolumeLockr

Length of output: 1109


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
test -f "$FILE" || { echo "Missing $FILE"; exit 1; }

echo "---- matches ----"
rg -n "setOnCancelListener|setNegativeButton|setOnDismissListener|switchButton|setOnCheckedChangeListener|registerSwitchButtonCallback" "$FILE"

echo "---- context around dialog (lines 160-240) ----"
nl -ba "$FILE" | sed -n '160,240p'

echo "---- context around switch handling (wider) ----"
nl -ba "$FILE" | sed -n '1,140p'

echo "---- context around registerSwitchButtonCallback ----"
rg -n "fun registerSwitchButtonCallback|registerSwitchButtonCallback" "$FILE" | head -n 50

Repository: jonathanklee/VolumeLockr

Length of output: 964


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
sed -n '60,130p' "$FILE"
echo "----"
sed -n '160,215p' "$FILE"

Repository: jonathanklee/VolumeLockr

Length of output: 5145


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"

sed -n '60,130p' "$FILE"
echo "----"
sed -n '160,215p' "$FILE"

Repository: jonathanklee/VolumeLockr

Length of output: 5145


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
rg -n "showTempUnlockDialog\\(" "$FILE"
echo "---- context around onVolumeLocked ----"
rg -n "private fun onVolumeLocked|onVolumeLocked\\(" "$FILE"
echo "---- print larger context around onVolumeLocked and any temp unlock logic ----"
sed -n '130,210p' "$FILE"

Repository: jonathanklee/VolumeLockr

Length of output: 3682


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
rg -n "showTempUnlockDialog\\(" "$FILE"
echo "---- context around onVolumeLocked/onVolumeUnlocked and showTempUnlockDialog call ----"
sed -n '210,280p' "$FILE"

Repository: jonathanklee/VolumeLockr

Length of output: 858


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
rg -n "onVolumeLocked\\(|onVolumeUnlocked\\(" "$FILE"
sed -n '180,260p' "$FILE"

Repository: jonathanklee/VolumeLockr

Length of output: 2011


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
sed -n '90,180p' "$FILE"

Repository: jonathanklee/VolumeLockr

Length of output: 3552


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
# get line numbers for onVolumeLocked definition
LINE=$(rg -n "private fun onVolumeLocked" "$FILE" | cut -d: -f1 | head -n1)
echo "onVolumeLocked at $LINE"
sed -n "$((LINE-40)),$((LINE+40))p" "$FILE"
# also show where showTempUnlockDialog called
CALL=$(rg -n "showTempUnlockDialog\\(" "$FILE" | head -n1 | cut -d: -f1)
echo "showTempUnlockDialog call at $CALL"
sed -n "$((CALL-20)),$((CALL+20))p" "$FILE"

Repository: jonathanklee/VolumeLockr

Length of output: 4800


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"
cat "$FILE" | head -n 220

Repository: jonathanklee/VolumeLockr

Length of output: 8356


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt"

echo "---- line numbers for onVolumeLocked/showTempUnlockDialog ----"
rg -n "private fun onVolumeLocked|showTempUnlockDialog\\(" "$FILE"

echo "---- onVolumeLocked block (around its definition) ----"
LINE_LOCK=$(rg -n "private fun onVolumeLocked" "$FILE" | cut -d: -f1 | head -n1)
sed -n "$((LINE_LOCK-40)),$((LINE_LOCK+120))p" "$FILE"

echo "---- showTempUnlockDialog call site (around its first call) ----"
LINE_CALL=$(rg -n "showTempUnlockDialog\\(" "$FILE" | cut -d: -f1 | head -n1)
sed -n "$((LINE_CALL-30)),$((LINE_CALL+60))p" "$FILE"

Repository: jonathanklee/VolumeLockr

Length of output: 8674


Back/outside cancel should also revert the switch state.

setOnCancelListener only clears mTempUnlockDialogShowing; dismissing the temp-unlock dialog via back/outside leaves holder.binding.switchButton checked even though onVolumeLocked() returned without adding a lock (the negative/cancel button handler already resets the switch).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/java/com/klee/volumelockr/ui/VolumeAdapter.kt` around lines 194
- 199, The cancel handler set in setOnCancelListener currently only clears
mTempUnlockDialogShowing but doesn't revert the UI state, so when the
temp-unlock dialog is dismissed via back/outside the switch remains checked;
update the setOnCancelListener to also reset
holder.binding.switchButton.isChecked = false (the same action performed in the
setNegativeButton lambda) and ensure any cleanup done in onVolumeLocked()
remains consistent with this UI change so the switch accurately reflects that no
lock was added.

android:orientation="vertical"
android:padding="16dp">

<!-- 启用开关 + 名称 -->
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use English for XML comments to keep codebase collaboration-friendly.

These inline comments are currently Chinese-only. Please switch them to English to match team review expectations and keep maintenance accessible to all contributors.

Also applies to: 54-54, 93-93, 117-117, 141-141

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/res/layout/dialog_time_slot.xml` at line 14, Replace the Chinese
inline XML comments in dialog_time_slot.xml with English equivalents to keep the
codebase accessible; specifically update the comment currently "启用开关 + 名称" and
the other Chinese comments at the same locations (lines showing the other UI
comment blocks) to clear English phrases (e.g., "Enable switch + label", "Start
time", "End time", "Repeat options" — adjust to match the actual UI elements) by
editing the XML comment nodes so they are in English; ensure the comments remain
as XML comments (<!-- ... -->) and preserve surrounding tags/IDs such as the
time slot layout elements to avoid changing structure.

android:layout_width="wrap_content"
android:layout_height="40dp"
android:minWidth="0dp"
android:text="08:00"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid hardcoded default time literals in layout.

"08:00" / "22:00" should come from resources (or be set at runtime with locale-aware formatting) to keep localization behavior consistent.

💡 Minimal XML-side adjustment
-                android:text="08:00"
+                android:text="`@string/schedule_default_start_time`"
...
-                android:text="22:00"
+                android:text="`@string/schedule_default_end_time`"

Also applies to: 137-137

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/res/layout/dialog_time_slot.xml` at line 113, The layout
currently hardcodes time literals via android:text="08:00" (and similarly
"22:00"); replace those static literals with string resources (e.g.,
`@string/default_start_time` and `@string/default_end_time`) or remove the
android:text and set the default times at runtime using locale-aware formatting
(e.g., DateFormat/TimeFormatter) in the containing view/activity/fragment.
Update dialog_time_slot.xml to reference the new string resource keys and add
corresponding entries to strings.xml, or clear the android:text attributes and
set the formatted time in the code path that inflates the dialog (use the view
IDs for the time TextViews to locate them).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants