Repository navigation
Release/2.6 - #160
Draft
faultables wants to merge 34 commits into
Draft
Release/2.6#160faultables wants to merge 34 commits into
faultables wants to merge 34 commits into
Conversation
…branch Staging: release/2.6 branched from develop (post 2.5 merge). MARKETING_VERSION 2.5 → 2.6 and build 304 → 305 on the flo target's Debug/Release configs, matching the release/2.5 bump convention (872aebb).
Hide the purple glyph layer in dark appearance (Icon Composer) so dark mode shows the white glyph on the auto-dark background. Also refresh the classic AppIcon dark PNG to match.
…uild The mac fetch_and_increment_build_number queried latest_testflight_build_number without a platform (default ios) while building the macOS train, and read the repo number via agvtool (which returns the watch target's 211, not the flo target's 305). On release/2.6 that computed max(ios 213, 211)+1 = 214 for the osx train already at 320 → 409 CFBundleVersion must be higher than 320. Query platform osx in the mac lane, and read the repo number as the max CURRENT_PROJECT_VERSION over all configs via Xcodeproj in both lanes.
The version-scoped osx query finds nothing for a fresh 2.6 while Apple enforces macOS build numbers across versions (osx 2.5 already at 320), so the guard fell back to the repo number and built 306 → 409 must be higher than 320. Drop the version filter in the mac lane so next is max(any osx build, repo)+1 = 321.
- Window title follows the active tab (Scene title + visible titlebar); blank the duplicate in-toolbar page headings on Catalyst. - Scope the Catalyst 900x500 min-size clamp to UINS* windows only: the global resize observer forced it onto NSPopupMenuWindow/popovers, blowing every Menu and context menu up to 900x500 (issue #158). - Toolbar ellipsis menus use native button style + fixed icon-only labels; song context menus drop the VStack wrapper and use native Labels. - Floating player: no drop shadow on Catalyst; AirPlay icon turns blue when a route is connected instead of showing the device name. - Albums grid: 5 columns on Catalyst. - Sidebar bottom bar: logged-in username (last-known/Offline fallback) with a Logout menu. - Search: Catalyst hosts a custom field in the window toolbar (title area) via catalystAwareSearch; other platforms keep the drawer. AlbumsView inline field is iOS-only now. - Preferences: hide the App Icon section when alternate icons are unsupported instead of showing a notice.
…ranch navigationTitle(_:displayMode:) does not exist; the older navigationBarTitle spelling with .automatic renders identically. (Local Catalyst builds never compiled the iOS branch, so this slipped through.)
- Draw the page heading in the navigation bar (leading, system font, primary colour) and hide the titlebar title, so it shares a row with the search field and toolbar actions instead of sitting on a second row. - Opt the search field out of the macOS 26 toolbar shared background so it reads as a separate control next to the filter menu. - Lift the content column's navigation stack so the header sits 8pt below the window top instead of under a ~41pt empty titlebar strip. The SwiftUI host view is lifted rather than the navigation view, so SwiftUI re-inserting the navigation view on page changes can't drop the header back to the unlifted position. - Add Catalyst-only headings for Home and Library (v2).
The floating player flashed during playback and popped out/in on every tab switch in the macOS/iPad sidebar layout. Two compounding defects: - The bar lived in an overlay inside every tab's content (sidebarTabContent), so TabView tore it down and re-created it with each tab's content and kept duplicates across the tabs it keeps mounted. Mount it once beside rootTabView, outside the TabView, keeping its content-column centering via the sidebar and side-panel insets. - The 1s playback tick persisted nowPlayingProgress to UserDefaults on every tick. Every @AppStorage in the app is backed by UserDefaults, so ContentView re-ran every second while playing and UIKit rebuilt its tab bar items (the FLO-36 _tabs_rebuildTabBarItemsAnimated: path) — re-laying out the subtree the bar lived in, once per second. Persist progress on a 5s throttle and flush it on pause/seek instead.
FLO-36: let users pin library albums, artists and playlists for quick access. Pins persist in UserDefaults (with one-time migration from the previous downloads-only format) and surface in the Catalyst/iPad sidebar Pinned section, the Library Pinned rows, and pin-first download grids. Adds LibraryDestination.playlist so pinned playlists deep-link through every navigation stack, and integrates pinned tabs with the existing tab-selection clamp. 285 tests, 0 failures.
- Pinned sidebar tabs, Library rows and v2 cards now show real cover art (new PinArtworkView handles remote/local/fallback) instead of kind icons; sidebar uses a custom Tab label. - Downloads Pinned/All-downloads headers: more horizontal spacing, pin glyph removed. - Pin menus use specific titles (Pin album/artist/playlist).
PinArtworkView no longer renders a tinted icon tile when art is missing/loading; it shows the app placeholder like every other artwork surface.
- Pin/Unpin context menus now on v2 recently played, recently added, albums, songs and liked-songs rails (song rows offer Pin album for the song's album). - v2 pinned cards grow to album size (148pt). - Catalyst sidebar pinned tabs back to icon + title, no cover.
- one-row control bar: round Play/Shuffle (icon-only) plus inline Play Artist Radio / Play Top Songs pills - ArtistDetailViewModel.fetchArtistSongs aggregates per-album tracks - pinned artist/album tabs wrapped in NavigationStack so inner album/artist links navigate - Catalyst pinned tabs show the item title as nav heading; window title resolves via displayName
…page-to-allow-entire' into release/2.6
Bake FLO_CHANNEL at build time (fastlane mac lane stamps beta/preview/store) and render it as plain text next to flo in the Catalyst sidebar header.
…ned subtitle, wider player + now-playing menu - Center pad floating player over content column with realistic sidebar widths (320 iPad / 260 Catalyst) - Albums tab: single search field (native .searchable on iOS, custom toolbar field on Catalyst only) - Right panel flush against content (gutter 0); seam is the panel's own divider so top-bar hover shows no empty strip - Library v2 Pinned section subtitle: You pinned it! - Catalyst floating player maxWidth 860 -> 1080 - Floating player now-playing ... menu left of lyrics: Go to Album, Go to Artist, Pin/Unpin album
Play Artist Radio / Play Top Songs swapped the whole label for a spinner, shifting layout. Now only the leading icon swaps (fixed 20x20 slot) and the text stays put.
Presets picker (Off + 23 presets) in Preferences > Experimental. Per-track audio tap applies 10 peaking biquads live; Off/Flat bypasses with nil mix. Persists via UserDefaults.
Off/Flat attaches no tap, so Off->preset left the playing song unequalized until the next track. Observe preset changes and (de)attach the mix when bypass state flips; preset-to-preset updates keep flowing through the tap's live gains.
- Persist queue order via new QueueEntity.position; snapshot/replace persistence in PlaybackService - Play Next / Play After / Add to Queue for songs, albums and playlists (local-first resolution, offline-capable), wired into every song list and album/playlist grid context menu - Queue rows: reorder (List onMove on iOS, drag-and-drop with commit-on-release previews on Catalyst/pad), swipe delete, Move to Top, Download/Remove Download, Like/Unlike, Go to Album/Artist, Clear Queue - Unify phone sheet and pad/Catalyst sidebar on shared QueueSupport (QueueRowStore, QueueRowMenu, drop delegates)
Derive the sidebar inset from the live TabView detail width reported by sidebarTabContent instead of a hardcoded 320pt estimate, so the bar stays centered over the content column in portrait, landscape, Stage Manager and collapsed-sidebar states.
Committing every DetailColumnWidthKey report straight into state cascaded preference -> state -> full ContentView/TabView re-render on every tick of a continuous window resize; the work per tick exceeded the tick interval and the main thread never caught up (beachball until the drag ends, panel open or not). DetailWidthDebouncer stages reports without touching view state and commits only after 200ms of layout quiet (plus 1pt epsilon), so resizes stay at the baseline single-pass cost and the player recenters once the layout settles.
nowPlaying force-subscripted the queue, so any reader evaluating in the teardown window after the queue empties but before the presence gates flip crashed with Index out of range. It now returns a blank sentinel from a store-less scratch context (never saved/merged/ deleted) when the index is out of bounds; getAlbumCoverArt returns early when empty and removeFromQueue guards a stale active index. Adds QueueEmptyCrashTests covering remove-last, clear, and fresh-empty reads.
…dit-mode reorder, output name above progress
…dismiss, top-aligned icons, artist play color, catalyst centering, iphone-only swipe-to-close
…max-reduce detail measurement + sidebar clamp
…, no sheet chrome
…zy list, long-press reorder
… 4px list inset, edit-gated menu, queue bottom bar, v1 ios search, neutral download button
…r, drag dim reset, v2 albums search, accent download button
…e cache stall Session/ghost-login: - Detect Subsonic HTTP 200 + status=failed (error code 40/41) as an auth failure in SubsonicEndpointRequest; non-auth failure codes (10, 20, ...) do not log out. Notify .sessionExpired from star/unstar and both download paths too (they previously bypassed ghost-session recovery). - Re-verify the restored session on launch, on .networkBecameOnline and on app foreground, so a launch while the server was down no longer leaves a ghost isLoggedIn=true (verifyExistingSession, guarded against overlap and stale sessions). Status dot / NetworkMonitor: - HomeView dot is now a true tri-state: green only when logged in AND the server is reachable; orange when logged in but unreachable or logged out of a configured server; red on fresh install. - Keep serverURL on logout so the dot can distinguish expired from fresh install and re-login is one tap with the URL prefilled. - NetworkMonitor: generation-guarded probes fix the orphaned-timeout race (a stale probe can no longer overwrite a newer verdict), 30s periodic and app-active re-probes keep the flag fresh, offline transition cancels in-flight probes, and probeServerReachability(completion:) reports the verdict so callers stop reading a stale flag. - ScrobbleQueueManager flushes only after the probe verdict lands (manual retry works first try when the server returns). Cache stall: - AlbumViewModel.refreshFailed flag: set on any failed load/refresh, cleared on success. LibraryView shows an orange 'Couldn't reach your server - showing cached library' banner with Retry in both v1 and v2; the cache is intentionally kept on offline refresh (stale beats empty). V2 refreshable chains deduped into refreshLibraryContent(). Catalyst sidebar: - The bottom-bar menu always showed Logout regardless of login state and had no login entry point. It now offers Login... when logged out and 'Logged in as <name>' + Logout when logged in. Tests: - Subsonic failed status 40 posts .sessionExpired; non-auth failure code does not; ND 401 regression guard; refreshFailed lifecycle.
Queue the next track behind the current one and let AVQueuePlayer advance without tearing down the player item. Gapless: - primeGaplessNext() enqueues the next track at activation when it is already local (offline download or StreamCacheManager cache), so the transition needs no network. - A cacheSong completion hook enqueues the local file the moment the pre-cache lands; otherwise the periodic observer enqueues the remote stream 10s before the end. - currentItem KVO drives auto-advance state sync (index, progress, metadata, lyrics, scrobble-server, star); repeat-once queues a duplicate item, repeat-album wraps to index 0. - Manual skips reuse the preloaded item via advanceToNextItem() and fall back to setNowPlaying() otherwise. - Queue edits, reorder, shuffle and repeat-mode changes resync the preload; the replaceCurrentItem-era path remains the fallback when nothing is queued (end of queue, radio, preload failure). Failure supervision: - Observe the queued item's status; a failed preload is dropped and not re-armed for that index until the track/queue changes. - A failed current item surfaces the error and auto-skips to the next distinct track, capped at 3 consecutive skips. - DidPlayToEndTime with a queued item arms a 1.5s watchdog that falls back to a manual swap if AVQueuePlayer does not advance. - setWillPlayNext() protects the preloaded file from cache eviction. - Scrobble captures the track identity before the async submission so a very short track cannot scrobble the next one. - EQ mix is refreshed on bind, since a pre-queued item may predate a preset change. Tests: 9 new cases pin next-index and failure-skip resolution across repeat modes. Docs updated in README and .wiki/features/audio-playback.md.
Crossfade (Preferences -> Experimental, Off by default): - dual AVQueuePlayer design: the incoming track plays on a second player while the outgoing one fades out with an equal-power 20 Hz ramp over the actual time remaining, so the incoming track peaks exactly at the outgoing track's end. - finishCrossfade promotes the incoming player to primary on ramp completion or on the outgoing item's DidPlayToEndTime, re-subscribing currentItem KVO and the periodic time observer and syncing now-playing, lyrics and scrobble state. - pause, seek, skip, queue edits, clear-queue, and turning crossfade off cancel an in-flight fade; a failed incoming item cancels instead of fading into silence. The volume slider does not fight the ramp. - Duration picker offers Off, 3, 5, 6, 8, 10 and 12 seconds; a positive duration enables crossfade and suppresses gapless preloading. Gapless improvements: - preloaded items now buffer with the system default instead of the 3 s stall-recovery cap; the cap is applied only once the item is current. - when the stream cache completes after a remote preload was queued, swap it for the local file (remote -> local upgrade). - refresh the EQ audio mix at bind only when the bypass state changed, instead of rebuilding a live MTAudioProcessingTap on every transition. - transition diagnostics on Console.app (net.faultables.flo / player) gated behind the Debug setting: preload source, deadline arming, auto-advance, watchdog/fallback, and failure paths. Tests: crossfade duration defaults to off, persists, and drives the enabled state. 307 tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.