Commit Graph

68 Commits

Author SHA1 Message Date
Arkadiusz Fal
84db5d0c42 Use List instead of Form for View Options on tvOS
Form inside a sheet causes clipped rows and invisible text on focused
items due to white-on-white rendering. Use a plain List on tvOS which
handles the focus styling correctly.
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
310869fad8 Add horizontal padding to View Options sheet on tvOS
Prevents form rows from being cut off at the left and right edges
of the narrow tvOS sheet.
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
58c3bdc0b6 Wrap View Options sheet in NavigationStack on tvOS for picker labels
The .pickerStyle(.menu) hid labels. Instead, wrap the Form in a
NavigationStack on tvOS so the default navigation picker style works
and shows both labels and values.
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
338127c692 Fix disabled pickers in View Options sheet on tvOS
Use .pickerStyle(.menu) for Row Size, Columns, and Channel Strip
pickers on tvOS so they work inside a sheet without NavigationStack.
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
893878c8a3 Update localizable 2026-04-18 20:38:01 +02:00
Arkadiusz Fal
d62ba1e143 Hide list row dividers and card clipping on tvOS
Dividers inside rows conflict with the tvOS focus highlight effect.
Remove dividers and the inset card background/clipShape on tvOS so
the focus effect renders cleanly without visual artifacts.
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
3c7581de1a Replace search content type segmented picker with Menu on tvOS
Unifies the filter strip on tvOS so all filters (sort, date, duration,
content type) use the same inline Menu style instead of mixing menus
with a segmented picker.
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
831773a609 Replace search filters sheet with inline menus on tvOS
The filters sheet is too small and awkward on tvOS. Replace the filter
button with inline Menu pickers for Sort By, Upload Date, and Duration
directly in the filter strip. Applied to both SearchView and
InstanceBrowseView.
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
bcb0864fca Fix tvOS search view: replace searchable with inline TextField, fix clipped focus
Use inline TextField with focusSection instead of .searchable() on tvOS
to prevent keyboard/navigation title overlap. Remove clipShape on recent
search items so tvOS focus effect is not cut off.
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
bbeb38ecf0 Hide link action, clipboard, and handoff settings on tvOS
These features are not available on Apple TV: clipboard monitoring,
default link action (no tap/share), and Handoff continuity.
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
5ae1fc3f29 Fix tvOS instance browse view overlapping search and navigation UI
Use inline TextField with focusSection instead of .searchable() and
.navigationTitle() on tvOS, matching the pattern in HistoryListView.
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
4b245ec176 Improve tvOS settings layout: use navigation instead of sheets, fix focus clipping
- Replace sheets with navigationDestination for Add/Edit Source on tvOS
  (tvOS sheets have fixed size that doesn't fit the content)
- Fix focused cell clipping by replacing TVSettingsContainer's frame-based
  layout with safeAreaInset, matching the main settings view pattern
- Use standard List with .listStyle(.grouped) for Sources on tvOS
- Add sidebar icons and titles to TVSettingsContainer for all settings
  subviews, utilizing the left column space
- Remove redundant large navigation titles on tvOS (shown in sidebar)
- Move Edit Source Save button from toolbar into form above Delete button
  for better tvOS focus navigation
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
b9a6d76ab3 Fix original audio detection so dubbed tracks don't play by default
parseAudioInfo() returned isOriginal=false for all streams when the
audioTrack object was present in the API response, preventing xtags
parsing from correctly identifying original tracks. This caused the
player to fall through to codec/bitrate sorting, often picking a
locale dub (e.g. Polish) instead of the original English audio.

Now determines isOriginal from both the audioTrack displayName
("original" keyword) and URL xtags (acont=original) for robustness.
Also adds isDefault to InvidiousAudioTrack for future use.
2026-04-18 20:38:01 +02:00
Arkadiusz Fal
eefd49f743 Fix three basic-auth regressions surfaced by end-to-end testing
- InstanceDetector: a single 401 from one probe was over-eagerly concluded
  as "credentials invalid" / "credentials required". On instances behind a
  reverse proxy where one probe path (e.g. Yattee Server's /info) hits a
  same-origin redirect, iOS URLSession strips the Authorization header on
  the redirect and the request 401s even with valid credentials. Track 401s
  across all probes and only conclude basicAuthRequired/basicAuthInvalid
  when no probe matched and at least one returned 401.

- InstanceLoginView: the Invidious/Piped login flow constructed an API
  client backed by the shared appEnvironment.httpClient, which has no
  per-instance basic-auth headers. For instances behind a reverse proxy,
  the login POST 401d before reaching the upstream login endpoint. Build a
  per-instance HTTPClient with the basic-auth Authorization header baked in
  via setDefaultHeaders, mirroring ContentService.httpClientWithBasicAuth.

- InvidiousAPI.login: the login function constructs its own URLSession (to
  capture Set-Cookie via a redirect-blocking delegate), so it never
  inherits headers from the injected httpClient. Add an optional
  extraHeaders parameter and have InstanceLoginView pass the basic-auth
  header through when present. PipedAPI.login uses httpClient.fetch and
  inherits defaultHeaders correctly, so no change is needed there.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
3dd4073db7 Allow HTTP Basic Auth credentials for any remote-server instance type
EditSourceView now exposes the basic-auth username/password fields for every
instance type (Invidious, Piped, PeerTube, Yattee Server), keeping the
existing required-credentials UI for Yattee Server and adding an optional
section for the others. Credentials are loaded and persisted via
BasicAuthCredentialsManager regardless of type, and clearing both fields
deletes stored credentials for non-Yattee types.

AddRemoteServerView gains a new basicAuthRequired UI state: when instance
detection hits a 401 (the entire instance is behind a reverse proxy), the
view reveals username/password fields and a Retry Detection button. The
retry calls the detector with the credentials injected as an Authorization
header; on success the form transitions into the normal detected state with
the credentials pre-populated. A repeat 401 shows an inline "invalid
credentials" message instead of restarting the flow. For non-Yattee types,
any credentials entered during the flow are persisted alongside the new
instance.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
222b53d520 Surface 401 from instance detection so the user can supply credentials
When an instance sits behind a reverse proxy that requires HTTP Basic Auth,
every detection probe (/info, /api/v1/config, /api/v1/stats, /healthcheck,
/config) returns 401 before reaching the real backend, so the type cannot be
identified. Re-throw APIError.unauthorized from each probe instead of
swallowing it, and have detectWithResult convert the first 401 it sees into
DetectionError.basicAuthRequired. Add a basicAuthHeader parameter so the
caller can retry detection after the user provides credentials; if a retry
also returns 401, surface basicAuthInvalid instead.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
63f1cb1f25 Inject basic auth via per-instance HTTPClient default headers
Replace the YatteeServerAPI setAuthHeader/buildHeaders pattern (which was
race-prone on the shared actor across multiple instances) with a generic
mechanism: HTTPClient now supports a defaultHeaders dictionary applied to
every request, and ContentService builds a per-instance HTTPClient with the
basic-auth Authorization header baked in whenever credentials are configured.

The same code path now works uniformly for Invidious, Piped, PeerTube, and
Yattee Server, so any instance sitting behind a reverse proxy that requires
HTTP Basic Auth can be authenticated regardless of backend type. Cached
default API actors are still reused when no basic-auth header is needed.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
aed78c13fb Send Piped auth token via query parameter instead of header
Piped accepts the session token via either an Authorization header or an
authToken query parameter (the /feed endpoint already uses the latter form).
Switch all token-bearing Piped endpoints to the query-parameter form so the
Authorization header is free for HTTP Basic Auth from a fronting reverse
proxy. Affects subscriptions, subscribe, unsubscribe, userPlaylists, and
userPlaylist (including its nextpage pagination loop).
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
8cd3aca96c Generalize Yattee Server credentials manager to BasicAuthCredentialsManager
Renames YatteeServerCredentialsManager → BasicAuthCredentialsManager so the
same Keychain-backed username/password storage can be reused for any instance
type that sits behind a reverse proxy requiring HTTP Basic Auth. Adds a
one-time migration that moves existing items from the legacy
'com.yattee.yatteeserver' Keychain service to 'com.yattee.basicauth',
preserving the iCloud-sync attribute. No behavior change for end users.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
240cf23693 Fix uneven shortcut card heights on tvOS home screen
Always reserve space for the subtitle line so cards with and without
subtitles have consistent heights in the grid.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
0071e1b117 Skip notifications for upcoming/premiere videos
Videos that haven't premiered yet were triggering repeated notifications
on every background refresh cycle. Filter them out by checking isUpcoming
flag and rejecting videos with future publish dates.

Also decode isUpcoming/premiereTimestamp from Yattee Server feed responses
instead of hardcoding false/nil.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
d6d15df105 Deduplicate time formatting and clean up unused code
Extract shared TimeInterval.formattedAsTimestamp replacing 8 identical
formatTime/formattedTime implementations across player views. Remove
unused currentTime parameter from GestureSeekPreviewView. Consolidate
duplicated geometry math in MacOSControlBar into seekPreviewPosition().
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
9b734f49ad Add separate glass capsule for chapter title above seek preview
Extract chapter title from inside the storyboard preview into a
standalone ChapterCapsuleView with its own glass capsule background.
The capsule follows the seek position horizontally but independently
clamps to screen edges using alignmentGuide, allowing it to be wider
than the storyboard thumbnail without going offscreen.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
4e8959d2df Fix missing leading padding on instance content section headers 2026-04-18 20:38:00 +02:00
Arkadiusz Fal
f3061763da Show seek time preview when no storyboards available
Display a floating time pill above the seek bar during dragging
(iOS) and dragging/hovering (macOS) when video has no storyboard
thumbnails. Includes chapter name when available.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
a7e5ebb068 Fix 5 TestFlight crash types from builds 250-254
- Fix BGTaskScheduler assertion crash on Mac Catalyst by guarding all
  iOS background task APIs with isMacCatalystApp check
- Fix iPad popover crash in UIPopoverPresentationController by adding
  .presentationCompactAdaptation(.sheet) to all 27 confirmationDialogs
- Fix SwiftData assertion crash when accessing deleted Bookmark model
  properties during SwiftUI hit testing in BookmarkRowView
- Fix UICollectionView invalid item count crash on queue swipe-to-delete
  by using ID-based removal with withAnimation instead of stale index
- Fix Range crash in storyboard download when storyboardCount is zero
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
8e5947c558 Fix HTTP basic auth credentials being stripped from instance URLs
Preserve user:pass credentials in instance URLs so Invidious instances
behind nginx reverse proxies with HTTP basic auth work correctly (#926).
Add displayURL property to mask credentials in the UI.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
21da76a9ea Fix tests 2026-04-18 20:38:00 +02:00
Arkadiusz Fal
4edb012181 Hide theme and accent color settings on tvOS
These settings don't work well on Apple TV, so exclude the
ThemeSection, AccentColorSection, and the .preferredColorScheme/.tint
modifiers from tvOS builds.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
f8da242968 Fix ContentUnavailableView centering on Apple TV
On tvOS, ContentUnavailableView inside a Group doesn't expand to fill
available space — it sizes to content and aligns top-leading. Add
.frame(maxWidth: .infinity, maxHeight: .infinity) to all instances
so they center correctly in their parent containers.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
59ccef950b Fix HomeView data staleness on new watch entries, tab switches, and settings dismissal
Post watchHistoryDidChange notification when a new watch entry is inserted
during local playback progress updates (but not on every progress tick).
Reload Home data when switching back to the Home tab and when the Customize
Home sheet is dismissed.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
acb6fb284a Fix Home view showing zero counts after returning from background
onAppear only fires once when the view first appears, not on foreground
return. Add scenePhase observer to reload data when the app becomes active.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
8c24b12b9a Migrate localization keys to dotted format
Remove 32 non-dotted keys (16 unused format specifiers, 16 word keys)
and replace with properly namespaced dotted keys following the existing
convention (common.*, player.*, search.*).
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
64193911c7 Show toolbar buttons and tab picker during channel loading
Display the view options button, channel menu, and content type tabs
immediately when the cached header is shown, instead of waiting for
the full channel data to load. The spinner now appears only in the
content area below the tabs.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
e956075f3c Fix CFNetwork SIGABRT crash when creating download tasks on invalidated session
The background URLSession could be in an invalid state when downloadTask(with:)
is called, because invalidateAndCancel() is asynchronous internally. This adds
an ObjC exception handler to catch NSExceptions from CFNetwork, nil guards on
the session, and safer session lifecycle management (nil after invalidation,
finishTasksAndInvalidate for cellular toggle).
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
54f175b294 Fix BGTaskScheduler crash by moving registration to App.init()
Apple requires BGTaskScheduler.register() to be called during the app
launch sequence before the run loop starts. Moving it from .onAppear
(too late) to init() prevents the crash on TestFlight builds.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
d1e63e85a4 Apply Invidious proxy rewriting to download streams
Downloads were using direct YouTube CDN URLs even when proxiesVideos
was enabled. Apply the same proxyStreamsIfNeeded used by the player
to the download code path in ContentService.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
7c2a205e74 Fix Piped relatedStreams decoding crash on missing fields
Malformed items in relatedStreams (e.g., missing title) no longer crash
the entire JSON decode. Reuses the existing PipedVideoItem (renamed from
PipedPlaylistItem) graceful-decoding wrapper for all relatedStreams arrays
in PipedStreamResponse, PipedChannelResponse, and PipedNextPageResponse.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
6298b38cba Add video proxy support with live toggle for Invidious/Piped instances
Adds a "Proxy videos" toggle in instance settings that routes video
streams through the instance instead of connecting directly to YouTube
CDN. Includes auto-detection of 403 blocks and live re-application of
proxy settings without requiring app restart or video reload.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
2e37873a12 Remove duplicate navigation titles on tvOS
The sidebarAdaptable TabView already shows tab names in the sidebar
pill, so the large .navigationTitle() was redundant on tvOS.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
013514adc3 Fix tvOS onboarding background transparency and button styling
Add opaque black background to onboarding on tvOS to prevent Home
screen content from leaking through the fullScreenCover. Replace
toolbar Skip button with plain overlay button to avoid blurred
material style, and add tvOS card button style with default focus
on Continue button.
2026-04-18 20:38:00 +02:00
Arkadiusz Fal
2f1e699623 Update localizable 2026-04-18 20:37:35 +02:00
Arkadiusz Fal
e6834b6eff Add DEV badge on iCloud settings for debug builds
Shows an orange "DEV" capsule next to the iCloud row in Settings and a
development environment notice at the top of iCloud settings, helping
distinguish CloudKit dev environment from production during development.
2026-04-18 20:37:35 +02:00
Arkadiusz Fal
07003a36d7 Fix deleted playlists resurrecting from iCloud after app restart
Pending deletes were lost across app restarts because
recoverPersistedPendingChanges() never reconstructed CKRecord.ID
objects from persisted record names. Additionally, incoming iCloud
records for deleted playlists were blindly applied, and orphaned
playlist items in CloudKit would recreate placeholder playlists.

- Rebuild pendingDeletes array from UserDefaults on recovery
- Guard applyRemoteRecord against records pending local deletion
- Skip deferred items whose parent playlist is pending deletion
- Queue all playlist item deletions when deleting a playlist
- Clean up placeholder playlists for pending-delete playlists
2026-04-18 20:37:35 +02:00
Arkadiusz Fal
e38e4cca3a Fix feed channel filter avatars showing placeholders instead of images
The filter strip was passing the Invidious instance URL as serverURL to
AvatarURLBuilder, which built a Yattee Server-style /avatar/ path that
doesn't exist on Invidious. Now passes the actual Yattee Server URL
(matching SubscriptionsView pattern) and enriches channels from
CachedChannelData as a fallback when the API doesn't return thumbnails.
2026-04-18 20:37:35 +02:00
Arkadiusz Fal
7c33f7e9f3 Change default layout settings 2026-04-18 20:37:25 +02:00
Arkadiusz Fal
7cea57c343 Update media browser view options sheet layout 2026-04-18 20:37:25 +02:00
Arkadiusz Fal
d045a64b63 Persist media browser view options per source
Save sort order, sort direction, and show-only-playable filter to
UserDefaults keyed by source ID so preferences survive navigation.
2026-04-18 20:37:25 +02:00
Arkadiusz Fal
7abd3a86fc Move close video button from toolbar into now playing card in RemoteControlView 2026-04-18 20:37:25 +02:00
Arkadiusz Fal
5c82c37339 Add Enable All / Disable All menu to channel notifications settings 2026-04-18 20:37:25 +02:00