Skip to content

feat: matrix status updates#3366

Open
gamesguru wants to merge 7 commits intoMetrolistGroup:mainfrom
gamesguru:guru/feat/matrix-status-updates
Open

feat: matrix status updates#3366
gamesguru wants to merge 7 commits intoMetrolistGroup:mainfrom
gamesguru:guru/feat/matrix-status-updates

Conversation

@gamesguru
Copy link
Copy Markdown

@gamesguru gamesguru commented Mar 27, 2026

Problem

Lack of support for a common, open-source, and decentralized chat and discourse platform, called Matrix, often referred to by its most popular client UI, "Element" messenger.

Cause

As above.

Solution

Add missing service and helper logic/functions.

Testing

Iteratively and with multiple accounts, but lightly with regard to status clearing, idling, unsetting.

Edit: Logic for idle/background unsetting of status has been improved.

My emphasis on this PR was a stable UX and minimal polling logic, since matrix servers can be sensitive to unnecessary presence updates, and the Metrolist UI thread as well likely appreciates a philosophy of minimalism with regard to outgoing (often totally redundant) network requests/updates.

Screenshots

Screenshot_20260330-103958 Screenshot_20260330-115017 Screenshot_20260330-103551 Screenshot_2026-03-27-00-00-30-65_f0d2a5fcaa6fbc4d841a0265ade1b0da

Summary by CodeRabbit

  • New Features

    • Matrix integration: manage Matrix accounts, enable/disable Matrix presence, customize status format and update interval, plus dedicated Matrix settings screen.
  • Behavior

    • Automatic presence/status updates and clears tied to playback, network, and screen events; supports debounced updates, periodic re‑updates, and manual clear with toast confirmation.
  • Chores

    • Encrypted token storage, new Matrix icon and localization strings, and added security crypto dependency.

	## COMMITS for REFACTOR 01

	implement multi-account status updates for Matrix
	don't update redundant (same) status msg. this avoids triggering excess client /sync reqs

	populate custom format box with default template for user convenience
	add placeholders as hints, infer scheme of homerserver if omitted

	consistent status string footer with preview and actual
	better handling of untrimmed inputs, HtTp:// case

	---

	## COMMITS for REFACTOR 02

	show [time elapsed / total] in Matrix status updates (from shared Discord stats)
	more aggressively clear statuses if screen off/player paused

	add option to configure update interval for Matrix
	set default to 15 second updates for Matrix

	strings in xml. obscure password/token at all stages
	add custom xml image icon for Matrix logo, `[m]`
	use dedicated/translatable strings.xml resources only
	impl secure token storage, thread-safety, force HTTPS for Matrix

	reformat INTENT section code/braces/indentation
	add docstrings

	consolidate loop; separate clear() and update() methods

	add mutex, scope, log. tidy up some lints.
	improved concurrency flow within updateMatrix
	lifecycle management, conflict cleanup
Copilot AI review requested due to automatic review settings March 27, 2026 23:44
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 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 Matrix presence integration: preference keys and account model, encrypted token store, MatrixRPC HTTP client, MusicService presence update/clear wiring, Matrix settings UI and navigation route, strings, drawable, and a security-crypto dependency.

Changes

Cohort / File(s) Summary
Preferences & Model
app/src/main/kotlin/com/metrolist/music/constants/PreferenceKeys.kt, app/src/main/kotlin/com/metrolist/music/models/MatrixAccount.kt
Added five DataStore preference keys for Matrix RPC and a @Serializable MatrixAccount data class (homeserver, userId).
Matrix RPC Client
app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt
New MatrixRPC class that sends HTTPS PUTs to Matrix presence endpoint, with template variable resolution, request deduplication, updateSong and clearStatus.
Token Storage
app/src/main/kotlin/com/metrolist/music/utils/MatrixTokenStore.kt
New MatrixTokenStore singleton using EncryptedSharedPreferences (lazy init) exposing saveToken, getToken, removeToken, with failure-safe logging.
Playback Integration
app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt
Integrated Matrix lifecycle: client list, mutexes/jobs, account JSON parsing, debounced status updates, clear/update methods, and DataStore collectors wired into playback/screen/network events.
Settings UI & Navigation
app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt, app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/IntegrationScreen.kt, app/src/main/kotlin/com/metrolist/music/ui/screens/NavigationBuilder.kt
Added MatrixSettings composable (add/edit accounts, token handling, status format, interval, clear status), integration card entry, and navigation route settings/integrations/matrix.
Resources & Asset
app/src/main/res/values/metrolist_strings.xml, app/src/main/res/drawable/matrix_icon.xml
Added string/plural resources and a new vector drawable matrix_icon.
Build Config
app/build.gradle.kts, gradle/libs.versions.toml
Added androidx.security:security-crypto dependency and version alias in libs.versions.toml.

Sequence Diagram(s)

sequenceDiagram
    participant Prefs as DataStore Prefs
    participant MS as MusicService
    participant Store as MatrixTokenStore
    participant MRPC as MatrixRPC Client
    participant Matrix as Matrix Server

    Prefs->>MS: MatrixAccounts/Enable/Format/Interval/Clear changed
    MS->>MS: rebuild clients / start/stop update jobs
    MS->>Store: getToken(homeserver,userId)
    alt token present
        Store-->>MS: accessToken
        MS->>MRPC: create MatrixRPC(homeserver,userId,accessToken)
        MS->>MS: add to matrixRpcClients
    end

    Note over MS,MRPC: Playback/network/screen events trigger updates
    MS->>MRPC: updateSong(song, currentPosition, statusFormat, presence)
    MRPC->>MRPC: resolveVariables & dedupe
    MRPC->>Matrix: PUT /_matrix/client/v3/user/{userId}/presence/{presence}
    Matrix-->>MRPC: HTTP response
    MRPC-->>MS: success/failure logged
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • nyxiereal
  • adrielGGmotion
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: matrix status updates' clearly and concisely summarizes the main change: adding Matrix presence/status update functionality.
Description check ✅ Passed The PR description includes all major template sections (Problem, Cause, Solution, Testing) with substantive content and provides additional context about design decisions and testing approach.

✏️ 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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds Matrix presence support so Metrolist can publish “listening” status to a user’s Matrix homeserver, with corresponding settings UI and playback-driven updates in MusicService.

Changes:

  • Introduces Matrix settings UI (accounts + status template) and navigation entry.
  • Adds Matrix presence client (MatrixRPC) and wires updates/teardown into MusicService.
  • Adds new preference keys and user-facing strings for the integration.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
app/src/main/res/values/metrolist_strings.xml Adds Matrix integration strings, status template strings, and an information warning.
app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt Implements Matrix Client-Server presence update calls and template variable resolution.
app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt Adds UI for enabling Matrix presence, managing accounts, and editing status format.
app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/IntegrationScreen.kt Adds Matrix entry to integrations list.
app/src/main/kotlin/com/metrolist/music/ui/screens/NavigationBuilder.kt Adds navigation route for Matrix settings screen.
app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt Creates/tears down Matrix clients and triggers presence updates based on playback and preference changes.
app/src/main/kotlin/com/metrolist/music/models/MatrixAccount.kt Adds serializable model for storing Matrix accounts.
app/src/main/kotlin/com/metrolist/music/constants/PreferenceKeys.kt Adds DataStore preference keys for Matrix enable/accounts/status format.

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

🧹 Nitpick comments (1)
app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt (1)

19-24: Reuse a shared OkHttpClient for Matrix accounts.

Each MatrixRPC builds its own client, while MusicService recreates these objects on settings/account changes. That needlessly multiplies connection pools and dispatcher threads, and close() does not tear them down; pass an app-scoped client instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt` around lines 19 -
24, MatrixRPC currently constructs its own OkHttpClient per instance
(constructor private val client: OkHttpClient = OkHttpClient()), which
multiplies connection pools and dispatcher threads when MusicService recreates
MatrixRPC on settings/account changes; change MatrixRPC to require an injected,
app-scoped OkHttpClient (remove the default new OkHttpClient()), have callers
(e.g., MusicService) pass a shared singleton client, and ensure
MatrixRPC.close() does not call
client.dispatcher().executorService().shutdownNow() or
client.connectionPool().evictAll() on the shared client (or documents that it
must not close the injected client).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt`:
- Around line 2969-2979: The updates race because cancelling matrixUpdateJob
doesn't stop the blocking HTTP call in MatrixRPC.updateSong; fix by serializing
updates using a Mutex: add a shared Mutex (e.g., matrixRpcMutex: Mutex) and
replace the current launch/cancel flow in updateMatrixRPC with a scope.launch {
matrixRpcMutex.withLock { val clients = matrixRpcClients.toList();
clients.forEach { client -> client.updateSong(song, statusFormat).onFailure {
Timber.tag(TAG).w(it, "Matrix RPC update failed") } } } } so each update waits
for the previous HTTP call to finish and preserves ordering.

In
`@app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt`:
- Around line 75-77: The Matrix access token is being persisted insecurely:
remove accessToken from rememberSaveable and from the serialized accountsJson
preference and instead store only non-secret metadata (homeserver and userId) in
your DataStore; move all token reads/writes to a secure store (e.g., Android
Keystore / EncryptedSharedPreferences or EncryptedFile) via a dedicated helper
(e.g., MatrixTokenStore.saveToken/getToken/removeToken) and update the UI code
(the accessToken variable and any code that builds accountsJson) to read/write
the token through that secure helper so tokens are never written into plain
preferences or saved-state.

In `@app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt`:
- Around line 55-68: The code in MatrixRPC builds a URL into the local variable
url then proceeds to send an Authorization bearer token without verifying the
scheme; add an explicit HTTPS enforcement right after building url (before
constructing Request and before any use of accessToken) by checking the parsed
URL's scheme (e.g., ensure the HttpUrl from toHttpUrlOrNull() isHttps or the url
string starts with "https://") and return early from the runCatching block if
the check fails so no bearer token is sent over plain HTTP.

---

Nitpick comments:
In `@app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt`:
- Around line 19-24: MatrixRPC currently constructs its own OkHttpClient per
instance (constructor private val client: OkHttpClient = OkHttpClient()), which
multiplies connection pools and dispatcher threads when MusicService recreates
MatrixRPC on settings/account changes; change MatrixRPC to require an injected,
app-scoped OkHttpClient (remove the default new OkHttpClient()), have callers
(e.g., MusicService) pass a shared singleton client, and ensure
MatrixRPC.close() does not call
client.dispatcher().executorService().shutdownNow() or
client.connectionPool().evictAll() on the shared client (or documents that it
must not close the injected client).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 47d9e728-d281-4b11-a3d8-b90d893b261a

📥 Commits

Reviewing files that changed from the base of the PR and between 5473408 and 5012e40.

📒 Files selected for processing (8)
  • app/src/main/kotlin/com/metrolist/music/constants/PreferenceKeys.kt
  • app/src/main/kotlin/com/metrolist/music/models/MatrixAccount.kt
  • app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt
  • app/src/main/kotlin/com/metrolist/music/ui/screens/NavigationBuilder.kt
  • app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/IntegrationScreen.kt
  • app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt
  • app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt
  • app/src/main/res/values/metrolist_strings.xml

@adrielGGmotion
Copy link
Copy Markdown
Collaborator

@gamesguru Never heard of that client but could i atleast see how this integration looks like at the "Matrix" app?

@nyxiereal
Copy link
Copy Markdown
Member

image

change the icon to the matrix icon

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/kotlin/com/metrolist/music/playback/MusicService.kt (1)

425-432: ⚠️ Potential issue | 🟠 Major

Serialize Matrix close/update requests per client.

matrixUpdateJob?.cancel() does not stop the in-flight synchronous execute() inside MatrixRPC.updateSong, and client.close() runs outside that job altogether. A quick pause/resume or account refresh can therefore let an older offline request finish after a newer online one and leave Matrix presence stale; queue these operations behind one mutex/actor instead of cancel-and-relaunch.

Also applies to: 849-857, 2308-2315, 2975-2985

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt` around
lines 425 - 432, The current cancel-and-close pattern around matrixUpdateJob and
matrixRpcClients leaves in-flight synchronous MatrixRPC.updateSong.execute()
calls running and allows client.close() to race with them; replace this with a
per-client serialized queue (e.g., a coroutine actor or a Mutex + single
coroutine) that accepts operations like UPDATE and CLOSE and executes them
sequentially for each Matrix RPC client, stop sending new updates by posting a
shutdown/close message to the actor instead of cancelling matrixUpdateJob
directly, and await the actor's completion before returning; update the logic
around matrixUpdateJob, MatrixRPC.updateSong invocations, and the loop over
matrixRpcClients/client.close() (also apply same change to the other
occurrences) so all update and close requests for a given client are executed
in-order.
app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt (1)

75-77: ⚠️ Potential issue | 🟠 Major

Keep Matrix access tokens out of saved state and plain preferences.

accessToken is still held in rememberSaveable and serialized into MatrixAccountsKey, so the secret lands in the saved-state bundle and plain DataStore. Persist only non-sensitive account metadata here and move the token to encrypted storage.

Also applies to: 165-167, 188-195

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt`
around lines 75 - 77, The accessToken is being stored in rememberSaveable (e.g.,
variable accessToken in MatrixSettings.kt) and thus serialized into
MatrixAccountsKey and plain DataStore; stop saving sensitive tokens in
saved-state or DataStore—keep only non-sensitive metadata (homeserver, userId)
in rememberSaveable/MatrixAccountsKey and move token storage to encrypted
storage (e.g., AndroidX Security: EncryptedSharedPreferences or an encrypted
DataStore wrapper). Update code paths that read/write accessToken (including the
instances around lines with accessToken and any save/load that touches
MatrixAccountsKey) to instead read/write the token via the encrypted store API
and ensure UI state uses a transient, non-saveable variable (e.g., remember
without rememberSaveable) or fetch token on demand.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt`:
- Around line 859-868: The code clears matrixRpcClients then decodes
accountsJson into List<MatrixAccount> but silently swallows exceptions; modify
the catch in the matrixEnabled branch to log the exception (including e and
accountsJson context) before returning emptyList(), e.g. use the existing logger
or processLogger to record a descriptive message about Matrix account decode
failure referencing matrixRpcClients/matrixEnabled/accountsJson/MatrixAccount so
operators see why Matrix presence was dropped.
- Around line 379-382: matrixRpcClients is accessed concurrently from multiple
coroutines (preference collector, updateMatrixRPC, and screen-state handlers)
causing race conditions; protect all reads and writes by introducing a single
Mutex (e.g. matrixRpcClientsMutex) and wrap every mutation and snapshot with
withLock { ... } (replace direct calls like matrixRpcClients.clear(), add(...),
and matrixRpcClients.toList() with guarded blocks), or alternatively confine all
access to a single dispatcher and switch callers to that dispatcher; update the
preference collector, updateMatrixRPC, and screen-state handler call sites so
every read/write uses the mutex-protected access to avoid
ConcurrentModificationExceptions and stale/duplicated clients.

In
`@app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt`:
- Around line 169-173: The current remember(accountsJson) block swallows JSON
decode errors by returning emptyList(), which hides malformed data and lets a
save overwrite it; instead, catch the exception from
matrixJsonSerializer.decodeFromString<List<MatrixAccount>>(accountsJson), log
the full error via your logger (or Timber) and surface a recovery state to the
UI (e.g., set an accountsParseError flag or provide a parsedAccounts nullable +
error message) so the UI can show an explicit "Restore/Reset/Import" option
rather than silently using emptyList(); update the code around the accounts
remember(...) block and any callers that consume accounts to handle the new
error state and avoid overwriting the original accountsJson until the user
chooses a recovery action.

---

Duplicate comments:
In `@app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt`:
- Around line 425-432: The current cancel-and-close pattern around
matrixUpdateJob and matrixRpcClients leaves in-flight synchronous
MatrixRPC.updateSong.execute() calls running and allows client.close() to race
with them; replace this with a per-client serialized queue (e.g., a coroutine
actor or a Mutex + single coroutine) that accepts operations like UPDATE and
CLOSE and executes them sequentially for each Matrix RPC client, stop sending
new updates by posting a shutdown/close message to the actor instead of
cancelling matrixUpdateJob directly, and await the actor's completion before
returning; update the logic around matrixUpdateJob, MatrixRPC.updateSong
invocations, and the loop over matrixRpcClients/client.close() (also apply same
change to the other occurrences) so all update and close requests for a given
client are executed in-order.

In
`@app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt`:
- Around line 75-77: The accessToken is being stored in rememberSaveable (e.g.,
variable accessToken in MatrixSettings.kt) and thus serialized into
MatrixAccountsKey and plain DataStore; stop saving sensitive tokens in
saved-state or DataStore—keep only non-sensitive metadata (homeserver, userId)
in rememberSaveable/MatrixAccountsKey and move token storage to encrypted
storage (e.g., AndroidX Security: EncryptedSharedPreferences or an encrypted
DataStore wrapper). Update code paths that read/write accessToken (including the
instances around lines with accessToken and any save/load that touches
MatrixAccountsKey) to instead read/write the token via the encrypted store API
and ensure UI state uses a transient, non-saveable variable (e.g., remember
without rememberSaveable) or fetch token on demand.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 847434b5-0f5c-4fe0-a96b-a8ae2b1723ce

📥 Commits

Reviewing files that changed from the base of the PR and between 5012e40 and 84c4dab.

📒 Files selected for processing (3)
  • app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt
  • app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt
  • app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt

@gamesguru gamesguru force-pushed the guru/feat/matrix-status-updates branch from 84c4dab to 3cefa32 Compare March 30, 2026 16:33
@gamesguru
Copy link
Copy Markdown
Author

change the icon to the matrix icon

@nyxiereal, please see updated screenshots on original post (above) and let me know if the aesthetics are acceptable. To me, the m feels a bit narrow and the [ ] braces, too, a bit cramped. Might benefit from a bit more spacious look?

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

🧹 Nitpick comments (5)
app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt (3)

2982-2982: Minor: Simplify boolean comparison.

dataStore.get(EnableMatrixRPCKey, false) != true can be simplified to !dataStore.get(EnableMatrixRPCKey, false).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt` at line
2982, Replace the verbose boolean comparison in MusicService.kt by negating the
stored value directly: locate the conditional using
dataStore.get(EnableMatrixRPCKey, false) != true (inside the MusicService class
or method handling RPC enabling) and change it to use the simpler form
!dataStore.get(EnableMatrixRPCKey, false) so the condition reads as a direct
negation of the retrieved boolean.

3038-3069: Consider releasing mutexes before network I/O.

The clearStatus() calls at line 3066 execute network requests while holding both matrixRpcUpdateMutex and matrixRpcClientsMutex. With multiple accounts or slow networks, this blocks all other Matrix operations for extended periods.

Consider snapshotting the client list and releasing locks before the network calls:

♻️ Suggested refactor
 private fun clearMatrixRPC(showToast: Boolean = false) {
     matrixUpdateJob?.cancel()
     matrixUpdateJob = scope.launch {
-        var count = 0
-        matrixRpcUpdateMutex.withLock {
-            matrixRpcClientsMutex.withLock {
-                // ... client loading logic ...
-                matrixRpcClients.forEach { it.clearStatus() }
-                count = matrixRpcClients.size
-            }
+        val clientsSnapshot: List<MatrixRPC>
+        matrixRpcUpdateMutex.withLock {
+            clientsSnapshot = matrixRpcClientsMutex.withLock {
+                // ... client loading logic (unchanged) ...
+                matrixRpcClients.toList()
+            }
         }
+        // Network calls outside locks
+        clientsSnapshot.forEach { it.clearStatus() }
+        val count = clientsSnapshot.size

         if (showToast && count > 0) {
             // ... toast logic ...
         }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt` around
lines 3038 - 3069, The code currently holds matrixRpcUpdateMutex and
matrixRpcClientsMutex while calling network I/O via MatrixRPC.clearStatus(),
which can block other operations; change this by snapshotting the clients list
while holding the locks (read matrixRpcClients into an immutable list), then
release both mutexes and iterate over the snapshot calling clearStatus() so
network calls happen outside of matrixRpcUpdateMutex and matrixRpcClientsMutex;
keep the existing logic that loads accounts into matrixRpcClients when it is
empty but only use that to build the snapshot before releasing locks.

871-871: Minor: Redundant == true comparison.

The expression dataStore.get(EnableMatrixRPCKey, false) == true can be simplified to just dataStore.get(EnableMatrixRPCKey, false) since the return type is already Boolean.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt` at line
871, In MusicService.kt update the conditional that reads
dataStore.get(EnableMatrixRPCKey, false) == true to just use the Boolean result
directly (dataStore.get(EnableMatrixRPCKey, false)); locate the if block in the
MusicService class where that check is performed and remove the redundant "==
true" comparison to simplify the expression and keep the same behavior.
app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt (1)

19-19: Consider configuring explicit timeouts for the shared OkHttpClient.

The sharedOkHttpClient uses OkHttp's default timeouts (10s for connect/read/write). Other parts of the codebase (e.g., ListenTogetherClient, OpenRouterService) configure explicit timeouts to handle slow or unresponsive servers more gracefully. While this may not cause immediate issues, adding explicit timeout configuration would improve resilience.

♻️ Suggested improvement
+import java.util.concurrent.TimeUnit
+
-private val sharedOkHttpClient: OkHttpClient = OkHttpClient()
+private val sharedOkHttpClient: OkHttpClient = OkHttpClient.Builder()
+    .connectTimeout(15, TimeUnit.SECONDS)
+    .readTimeout(15, TimeUnit.SECONDS)
+    .writeTimeout(15, TimeUnit.SECONDS)
+    .build()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt` at line 19, The
sharedOkHttpClient is created with default OkHttp timeouts; update the
OkHttpClient builder used for sharedOkHttpClient to set explicit
connect/read/write (and optionally call) timeouts to match project conventions
(similar to ListenTogetherClient/OpenRouterService); locate the
sharedOkHttpClient declaration and replace OkHttpClient() with a configured
OkHttpClient.Builder().connectTimeout(...).readTimeout(...).writeTimeout(...).callTimeout(...).build()
using sensible values consistent with other clients.
app/src/main/kotlin/com/metrolist/music/utils/MatrixTokenStore.kt (1)

10-10: Consider using Timber for logging consistency.

The codebase uses Timber for logging elsewhere (e.g., MatrixRPC.kt, MusicService.kt). Using android.util.Log directly is inconsistent and loses Timber's benefits (tree-based control, automatic tagging).

♻️ Suggested change
-import android.util.Log
+import timber.log.Timber
 
 // Then replace Log calls:
-Log.e(TAG, "Failed to initialize EncryptedSharedPreferences", e)
+Timber.tag(TAG).e(e, "Failed to initialize EncryptedSharedPreferences")
 
-Log.w(TAG, "saveToken: encrypted storage unavailable, token not persisted")
+Timber.tag(TAG).w("saveToken: encrypted storage unavailable, token not persisted")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/kotlin/com/metrolist/music/utils/MatrixTokenStore.kt` at line
10, Replace android.util.Log usage in MatrixTokenStore with Timber to match
project logging conventions: remove the import android.util.Log, add import
timber.log.Timber, and update any Log.d/Log.e/... calls inside the
MatrixTokenStore class or functions (e.g., within MatrixTokenStore methods that
log tokens or errors) to use Timber.d/Timber.e (or Timber.tag(...) if custom
tags are needed); ensure any exception messages passed to Log are forwarded to
Timber.e(exception, ...) to preserve stack traces.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt`:
- Around line 152-158: In resolveVariables, guard against a negative or unknown
SongEntity.duration (defaults to -1) before passing it to makeTimeString: check
song.song.duration and if it's negative or zero, pass 0 (or an empty/placeholder
string) to makeTimeString for the "{total_time}" replacement instead of
multiplying a negative value by 1000L; update the "{total_time}" replacement in
resolveVariables to compute totalMs only when duration > 0 (e.g., totalMs =
song.song.duration * 1000L else 0) so makeTimeString never receives a negative
millisecond value.

---

Nitpick comments:
In `@app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt`:
- Line 2982: Replace the verbose boolean comparison in MusicService.kt by
negating the stored value directly: locate the conditional using
dataStore.get(EnableMatrixRPCKey, false) != true (inside the MusicService class
or method handling RPC enabling) and change it to use the simpler form
!dataStore.get(EnableMatrixRPCKey, false) so the condition reads as a direct
negation of the retrieved boolean.
- Around line 3038-3069: The code currently holds matrixRpcUpdateMutex and
matrixRpcClientsMutex while calling network I/O via MatrixRPC.clearStatus(),
which can block other operations; change this by snapshotting the clients list
while holding the locks (read matrixRpcClients into an immutable list), then
release both mutexes and iterate over the snapshot calling clearStatus() so
network calls happen outside of matrixRpcUpdateMutex and matrixRpcClientsMutex;
keep the existing logic that loads accounts into matrixRpcClients when it is
empty but only use that to build the snapshot before releasing locks.
- Line 871: In MusicService.kt update the conditional that reads
dataStore.get(EnableMatrixRPCKey, false) == true to just use the Boolean result
directly (dataStore.get(EnableMatrixRPCKey, false)); locate the if block in the
MusicService class where that check is performed and remove the redundant "==
true" comparison to simplify the expression and keep the same behavior.

In `@app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt`:
- Line 19: The sharedOkHttpClient is created with default OkHttp timeouts;
update the OkHttpClient builder used for sharedOkHttpClient to set explicit
connect/read/write (and optionally call) timeouts to match project conventions
(similar to ListenTogetherClient/OpenRouterService); locate the
sharedOkHttpClient declaration and replace OkHttpClient() with a configured
OkHttpClient.Builder().connectTimeout(...).readTimeout(...).writeTimeout(...).callTimeout(...).build()
using sensible values consistent with other clients.

In `@app/src/main/kotlin/com/metrolist/music/utils/MatrixTokenStore.kt`:
- Line 10: Replace android.util.Log usage in MatrixTokenStore with Timber to
match project logging conventions: remove the import android.util.Log, add
import timber.log.Timber, and update any Log.d/Log.e/... calls inside the
MatrixTokenStore class or functions (e.g., within MatrixTokenStore methods that
log tokens or errors) to use Timber.d/Timber.e (or Timber.tag(...) if custom
tags are needed); ensure any exception messages passed to Log are forwarded to
Timber.e(exception, ...) to preserve stack traces.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 448e626b-5e94-4bfa-b85d-9deacd3914f8

📥 Commits

Reviewing files that changed from the base of the PR and between 84c4dab and 3cefa32.

📒 Files selected for processing (13)
  • app/build.gradle.kts
  • app/generate_proto.sh
  • app/src/main/kotlin/com/metrolist/music/constants/PreferenceKeys.kt
  • app/src/main/kotlin/com/metrolist/music/models/MatrixAccount.kt
  • app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt
  • app/src/main/kotlin/com/metrolist/music/ui/screens/NavigationBuilder.kt
  • app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/IntegrationScreen.kt
  • app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt
  • app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt
  • app/src/main/kotlin/com/metrolist/music/utils/MatrixTokenStore.kt
  • app/src/main/res/drawable/matrix_icon.xml
  • app/src/main/res/values/metrolist_strings.xml
  • gradle/libs.versions.toml
✅ Files skipped from review due to trivial changes (7)
  • app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/IntegrationScreen.kt
  • app/build.gradle.kts
  • app/src/main/res/drawable/matrix_icon.xml
  • app/src/main/kotlin/com/metrolist/music/ui/screens/NavigationBuilder.kt
  • gradle/libs.versions.toml
  • app/src/main/res/values/metrolist_strings.xml
  • app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/kotlin/com/metrolist/music/models/MatrixAccount.kt
  • app/src/main/kotlin/com/metrolist/music/constants/PreferenceKeys.kt

@gamesguru
Copy link
Copy Markdown
Author

gamesguru commented Mar 30, 2026

@gamesguru Never heard of that client but could i atleast see how this integration looks like at the "Matrix" app?

Certainly, @adrielGGmotion. Thanks for wanting to see some client screenshots!

There are multiple clients. Since it's an open-source and federated system, there's no "one" standard. But here's a few good examples.

Let me know if there's anything about the PR you'd like to see cleaned up. I'll still push minor edits, but it's taking more complete shape already.

Web (Cinny), and desktop client (nheko)

client_Screenshot_2026-03-30_at_10-29-08_Cinny client_Screenshot_20260330_102524_Nheko

Mobile client (FluffyChat)

client_Screenshot_20260330-101932_Fluffy client_Screenshot_20260330-103221_Fluffy

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 6 comments.

gamesguru and others added 3 commits March 30, 2026 16:57
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
… into guru/feat/matrix-status-updates-refactor-001
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: 2

♻️ Duplicate comments (2)
app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt (2)

85-91: ⚠️ Potential issue | 🟠 Major

Don't keep the Matrix access token in rememberSaveable.

rememberSaveable persists this secret outside MatrixTokenStore, so the token can land in saved instance state instead of only in encrypted storage. Keep it in plain remember state and reload it from MatrixTokenStore when the dialog opens.

🔐 Minimal fix
-    var accessToken by rememberSaveable {
+    var accessToken by remember(initialAccount?.homeserver, initialAccount?.userId) {
         mutableStateOf(
             initialAccount?.let {
                 MatrixTokenStore.getToken(context, it.homeserver, it.userId)
             } ?: ""
         )
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt`
around lines 85 - 91, The access token is being stored in rememberSaveable
(variable accessToken) which can persist secrets to saved instance state; change
it to use plain remember instead and load the token from
MatrixTokenStore.getToken(context, initialAccount.homeserver,
initialAccount.userId) whenever the settings dialog opens (e.g., when
initialAccount becomes non-null or on dialog entry) so the token remains only in
memory and stays managed by MatrixTokenStore; update the MatrixSettings
composable to replace rememberSaveable with remember and add a reload path that
reads the token from MatrixTokenStore when the dialog is shown.

317-324: ⚠️ Potential issue | 🟡 Minor

Don't offer “Add Account” while the stored payload is in recovery mode.

When decoding fails, accounts becomes empty, so this branch still lets the user save a fresh list over the malformed JSON without explicitly choosing the reset action.

🛠️ Simple guard
-        if (accounts.size < 3) {
+        if (accountsParseError == null && accounts.size < 3) {
             accountItems.add(
                 Material3SettingsItem(
                     title = { Text(stringResource(R.string.matrix_add_account)) },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt`
around lines 317 - 324, The "Add Account" entry is shown whenever accounts.size
< 3 even if the stored payload decoding failed and you're in recovery mode;
update the conditional that adds the Material3SettingsItem (the block
referencing accounts, isAdding, Material3SettingsItem) to also check that the
payload is not in recovery/decoded-failure state (e.g., add && !isRecoveryMode
or && payloadDecodedSuccessfully) so the "Add Account" option is suppressed
during recovery and users must explicitly perform the reset action instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt`:
- Around line 137-149: The confirmButton handler currently allows blank
homeserver, userId, or accessToken to be saved; update the onClick in
confirmButton to trim and validate inputs (homeserver -> normalizedHomeserver,
userId.trim(), accessToken.trim()) and only call onSave(MatrixAccount(...), ...)
when all three are non-blank; if any value is blank, abort the save (e.g.,
return early or disable the button) and surface an error/validation state so
incomplete credentials cannot be persisted. Ensure you reference the existing
symbols: confirmButton, homeserver, normalizedHomeserver, userId, accessToken,
onSave, and MatrixAccount.
- Around line 206-220: In onSave (lambda handling save flow) prevent creating
duplicate (homeserver, userId) entries before mutating accountsJson: check
accounts for any entry (use variable accounts) whose homeserver and userId match
the new account but whose index != editingIndex; if such a duplicate exists then
abort or surface an error instead of proceeding (do not call
MatrixTokenStore.saveToken or update accountsJson), otherwise continue with the
existing logic (remove old token for changed pair, save token via
MatrixTokenStore, update newAccounts and set accountsJson via
matrixJsonSerializer). Ensure the duplicate check covers both add (isAdding) and
edit paths and references MatrixAccount, editingIndex, isAdding,
MatrixTokenStore, accountsJson, and matrixJsonSerializer to locate the code.

---

Duplicate comments:
In
`@app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt`:
- Around line 85-91: The access token is being stored in rememberSaveable
(variable accessToken) which can persist secrets to saved instance state; change
it to use plain remember instead and load the token from
MatrixTokenStore.getToken(context, initialAccount.homeserver,
initialAccount.userId) whenever the settings dialog opens (e.g., when
initialAccount becomes non-null or on dialog entry) so the token remains only in
memory and stays managed by MatrixTokenStore; update the MatrixSettings
composable to replace rememberSaveable with remember and add a reload path that
reads the token from MatrixTokenStore when the dialog is shown.
- Around line 317-324: The "Add Account" entry is shown whenever accounts.size <
3 even if the stored payload decoding failed and you're in recovery mode; update
the conditional that adds the Material3SettingsItem (the block referencing
accounts, isAdding, Material3SettingsItem) to also check that the payload is not
in recovery/decoded-failure state (e.g., add && !isRecoveryMode or &&
payloadDecodedSuccessfully) so the "Add Account" option is suppressed during
recovery and users must explicitly perform the reset action instead.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a2e7719a-2c41-472b-9c46-4e1d3682ed9e

📥 Commits

Reviewing files that changed from the base of the PR and between 3cefa32 and 9adfaef.

📒 Files selected for processing (2)
  • app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt
  • app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt

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.

♻️ Duplicate comments (2)
app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt (2)

206-220: ⚠️ Potential issue | 🟠 Major

Reject duplicate (homeserver, userId) entries before saving.

MatrixTokenStore keys tokens by (homeserver, userId). Allowing two rows with the same identifiers means editing or deleting one can overwrite/remove the other's token.

🛡️ Add duplicate check before mutating state
-            onSave = { account, token ->
+            onSave = onSave@{ account, token ->
+                val duplicateIndex = accounts.indexOfFirst {
+                    it.homeserver == account.homeserver && it.userId == account.userId
+                }
+                if (duplicateIndex != -1 && duplicateIndex != editingIndex) {
+                    // TODO: Surface validation error to user
+                    return@onSave
+                }
+
                 val oldAccount = editingIndex?.let { accounts.getOrNull(it) }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt`
around lines 206 - 220, Before mutating state in the onSave handler, check for
an existing account with the same homeserver and userId and reject the operation
if found: in the onSave lambda (the block that uses editingIndex, isAdding,
accounts, MatrixTokenStore, matrixJsonSerializer and sets accountsJson) add a
duplicate check that scans accounts for an entry with matching homeserver and
userId (when editing, ignore the current editingIndex), and if a duplicate
exists abort the save (return/notify) without calling MatrixTokenStore.saveToken
or modifying accountsJson; this prevents two rows from sharing the same
(homeserver, userId) key and avoids token overwrite/removal issues.

137-149: ⚠️ Potential issue | 🟠 Major

Block saving incomplete account credentials.

The save button accepts blank homeserver, userId, or accessToken values and persists an account that cannot authenticate.

🛡️ Add validation to the save button
-            TextButton(
+            TextButton(
+                enabled =
+                    homeserver.isNotBlank() &&
+                    userId.isNotBlank() &&
+                    accessToken.isNotBlank(),
                 onClick = {
                     val trimmedServer = homeserver.trim()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt`
around lines 137 - 149, The confirmButton's onClick currently trims and
normalizes homeserver then unconditionally calls onSave with
MatrixAccount(normalizedHomeserver, userId.trim()) and accessToken.trim(),
allowing blank homeserver/userId/accessToken to be persisted; update the
TextButton handler in confirmButton to validate that trimmed homeserver (after
normalization), userId.trim(), and accessToken.trim() are non-blank before
calling onSave (otherwise prevent saving and surface a validation error or keep
the button disabled). Specifically, in the confirmButton/TextButton onClick for
MatrixSettings, compute trimmedServer/userId/accessToken, derive
normalizedHomeserver as now, then if any of normalizedHomeserver.isBlank(),
userIdTrimmed.isBlank(), or accessTokenTrimmed.isBlank() return early (or set a
local validation error state) instead of calling onSave(MatrixAccount(...),
...); ensure the same validation state is used to disable the save button when
appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt`:
- Around line 206-220: Before mutating state in the onSave handler, check for an
existing account with the same homeserver and userId and reject the operation if
found: in the onSave lambda (the block that uses editingIndex, isAdding,
accounts, MatrixTokenStore, matrixJsonSerializer and sets accountsJson) add a
duplicate check that scans accounts for an entry with matching homeserver and
userId (when editing, ignore the current editingIndex), and if a duplicate
exists abort the save (return/notify) without calling MatrixTokenStore.saveToken
or modifying accountsJson; this prevents two rows from sharing the same
(homeserver, userId) key and avoids token overwrite/removal issues.
- Around line 137-149: The confirmButton's onClick currently trims and
normalizes homeserver then unconditionally calls onSave with
MatrixAccount(normalizedHomeserver, userId.trim()) and accessToken.trim(),
allowing blank homeserver/userId/accessToken to be persisted; update the
TextButton handler in confirmButton to validate that trimmed homeserver (after
normalization), userId.trim(), and accessToken.trim() are non-blank before
calling onSave (otherwise prevent saving and surface a validation error or keep
the button disabled). Specifically, in the confirmButton/TextButton onClick for
MatrixSettings, compute trimmedServer/userId/accessToken, derive
normalizedHomeserver as now, then if any of normalizedHomeserver.isBlank(),
userIdTrimmed.isBlank(), or accessTokenTrimmed.isBlank() return early (or set a
local validation error state) instead of calling onSave(MatrixAccount(...),
...); ensure the same validation state is used to disable the save button when
appropriate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e18d1f4d-8c31-40d2-81f4-4cbeb5dcf7dd

📥 Commits

Reviewing files that changed from the base of the PR and between 9adfaef and ebdbb7f.

📒 Files selected for processing (3)
  • app/src/main/kotlin/com/metrolist/music/playback/MusicService.kt
  • app/src/main/kotlin/com/metrolist/music/ui/screens/settings/integrations/MatrixSettings.kt
  • app/src/main/kotlin/com/metrolist/music/utils/MatrixRPC.kt

@nyxiereal
Copy link
Copy Markdown
Member

all fine and dandy about the icon, but i would like you to make this switch material 3 expressive

image

look at omar's pr #3387

@gamesguru
Copy link
Copy Markdown
Author

gamesguru commented Apr 6, 2026

@nyxiereal Okay, so it should grey-out (disable) and show a cross-out or "x" symbol?

I think I have a few issues to clean up on this PR, the disable functionality doesn't always disable and sometimes it blocks clear calls sent manually via the clear button. The UI elements that stay vs. those that get hidden when disabled is also not the most intuitive, iirc.

Also, there may be some unintended changes regarding "casting" or nefarious switching of the audio source. Unsure if it's something else on my end, but it ends up thinking it's casting (not in actual Bluetooth mode), which means i have to manually dig into my Bluetooth or volume/settings menu to adjust the volume.

I'm also not completely sold on the slider UI or the fixed range of 5 to 120 or 180 seconds that I set for the update interval. Perhaps not the optimal user experience nor programming logic.

@nyxiereal
Copy link
Copy Markdown
Member

well, yeah (about the switch)

good luck with the issues

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.

4 participants