From e0e05e3b60d6613898c790d14bb5ee3dfca4222c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 09:30:35 +0000 Subject: Playback controls, background workers, settings The app can now actually play something. A MediaController binds the UI to the playback service rather than reaching for the ExoPlayer instance directly -- the service can restart independently of the activity, and a stale direct reference would fail with no obvious symptom. Resume goes through PlaybackProgress, so an episode whose stored position is past a re-encoded end restarts rather than seeking past the end and stalling. Skip is asymmetric: 15 seconds back for a missed sentence, 30 forward for an ad break. Workers: refresh every six hours on unmetered network only -- refreshing feeds is cheap but the downloads that follow are not, and a phone on mobile data should not discover that by itself -- and retention every twelve. Both enqueue with KEEP rather than UPDATE, since UPDATE resets the interval on every launch and on a frequently-opened app means periodic work effectively never fires. Retention's actual logic is in RetentionSweeper rather than the Worker, because it deletes a listener's audio and the failure mode is silent and permanent. It only clears the database row once the file is really gone: the other order orphans the file, since nothing else knows about it and the disk would never be reclaimed. A row pointing at an already-missing file is still cleared, or it stays a permanently unplayable DOWNLOADED entry. Settings reaches the three features that had tested logic and no way to run them: OPML import and export through the system file picker, playback speed, and the sleep timer. Import reports how many feeds failed, not just how many succeeded -- silently importing 40 of 60 only surfaces weeks later when a show never appears. ListenViewModel was split rather than raising detekt's function-count limit a second time. Playback controls operate on whatever is playing regardless of screen, which is a real seam rather than a count. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B --- config/detekt/detekt.yml | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'config') diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 3d282f5..7d8a663 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -3,6 +3,10 @@ # upgrading detekt pulls in sane new defaults automatically instead of us silently diverging. style: + MagicNumber: + ignorePropertyDeclaration: true # assigning a literal to a named property IS the fix this + # rule asks for -- `private val SPEEDS = listOf(1.0f, 1.25f, ...)` is named data, and + # exploding each element into its own constant would make it less readable, not more. ReturnCount: max: 5 # the feed parser is guard-clause heavy on purpose -- an enclosure with no URL, no # type, or a non-playable type each bail out early. Forcing those into nested conditionals -- cgit v1.2.3