Commit Graph

502 Commits

Author SHA1 Message Date
Arkadiusz Fal
c289e7576e Add per-page element customization and panel settings polish
Phase 5 of the player panels redesign:
- Per-page element toggles now rendered and editable for every page:
  comments (author avatars), related (thumbnails), chapters (thumbnails),
  queue (previously played section), tracks (video/audio/subtitles/speed
  sections)
- Page bar labels toggle (icons-only bar shows the selected page's name)
- Layout mode schematic diagram in the details panel settings
- NEW badge machinery for future page types (versionAdded on PanelPageType)
- Typed per-page settings accessors on PanelPageConfiguration and VM update
  helpers for each page kind
- Unit tests for per-page settings round-trip and accessor fallbacks

The standalone quality selector sheet is unaffected: section gating defaults
to everything visible and only the embedded tracks page passes preset
settings through.
2026-09-22 09:37:10 +02:00
Arkadiusz Fal
052cb76702 Add preset-driven details panel pages on tvOS
TVDetailsPanel now renders the active tvOS preset's panel pages (info,
comments, related, chapters, queue; tracks stays with the quality panel)
with runtime hiding of empty pages, a segmented picker for up to four
pages and a focusable capsule row above that, and Info element toggles
honored. Comments unify on the PlayerState loader; the tvOS queue list
is extracted from QueueManagementSheet into TVQueueListView and reused
as the queue page. New related/chapters focus-based pages: selecting a
related video plays it and dismisses the panel, chapters seek and keep
the panel open.

New minimal Settings > Player Panels screen edits the active tvOS
preset via the existing service: duplicate-to-edit, page toggles with
last-page guard, Move Up/Down reordering, and Info element toggles.
2026-09-21 23:54:14 +02:00
Arkadiusz Fal
a6d989c7af Add corner player layout on iPad and macOS
Preset-selected two-region layout: video in the top-left, a resizable
side column, and an optional region below the video. Pages are assigned
to regions per preset (Location picker in the page editor); the layout
falls back to the stacked widescreen layout below 900pt width, so Split
View and narrow windows degrade automatically.

- CornerPlayerLayout hosts the shared playerAreaForWidescreen builder,
  so the MPV hosting path is identical to the stacked layout
- Layout resolver in ExpandedPlayerSheet picks corner per layout pass;
  NavigationCoordinator.isCornerLayoutActive feeds the macOS window
  manager, which drops the aspect-ratio lock and auto-fit while corner
  is active (and restores them on exit, incl. fullscreen replay and
  window re-mounts)
- Side column width persisted via new cornerPanelColumnWidth setting
  (280-560pt, video column kept >= 480pt)
- Settings: layout mode picker (iPad/macOS) with automatic-fallback
  footer, per-page region picker when corner is on, region shown in
  the pages list
2026-09-21 23:52:09 +02:00
Arkadiusz Fal
c33d125553 Add details panel preset settings UI and default visibility
Phase 2 of the player panels redesign:

- New "Details Panel" screen in Player Controls settings: page
  reorder/enable list with per-page editor (info element toggles),
  and a default panel visibility picker (hidden/visible/remember).
- PlayerControlsSettingsViewModel gains panelSettings bindings with
  the usual debounced-save flow; the last enabled page cannot be
  disabled.
- Built-in presets bumped to v9 with explicit default panelSettings.
- The preset's default visibility now drives the wide-layout panel
  on player open and on entering landscape, replacing the hard
  hidden resets; "remember" restores the new detailsPanelLastVisible
  setting recorded on user toggles. Applied once per expanded player
  session (flag on NavigationCoordinator survives re-mounts).
- WideScreenPlayerLayout picks up external visibility writes so the
  async-loaded default reaches the mounted layout.
- Unit tests for PanelSettings Codable compatibility (round-trip,
  old JSON, unknown page types/discriminators/enum values); removed
  stale PlayerInfoTab tests left from Phase 1.
2026-09-21 23:25:10 +02:00
Arkadiusz Fal
d5285b2b83 Add unified page-based details panel on existing layouts
Phase 1 of the player panels redesign:

- New PanelSettings model (pages, regions, layout mode, default
  visibility) stored as an optional field on PlayerControlsLayout with
  an effectivePanelSettings accessor; lenient Codable so future page
  types and settings payloads never corrupt saved presets.
- New Views/Player/Panels content engine: PlayerPanelContentView
  renders a page bar plus the selected page (info, comments, related,
  chapters, queue, tracks), with runtime hiding of empty pages.
- PortraitDetailsPanel and FloatingDetailsPanel now host the shared
  content instead of their duplicated info bodies; drag machinery,
  pills, and fades are untouched.
- Comments list extracted into CommentsListContent shared by the
  overlay and the comments page; comments pill routes to the comments
  page (expanding the portrait panel) when the page is enabled, and
  falls back to the overlay when it is not.
- Tracks page embeds QualitySelectorView via a new isEmbedded mode.
- Deleted the unused video info bodies in ExpandedPlayerSheet+VideoInfo
  and the dead PlayerInfoTab model.
2026-09-21 23:20:45 +02:00
Arkadiusz Fal
8bb6137417 Lay groundwork for the player panels redesign
Phase 0 of the panels redesign, extraction only, no behavior changes:

- Unify comments loading on PlayerState: loadComments/loadMoreComments
  become the single loader, used by both the player sheet preload and
  the comments overlay (which previously had its own duplicate copy).
- Extract PanelResizeGrabber from FloatingDetailsPanel into the new
  Views/Player/Panels/ directory.
- Extract the shared queue list from QueueManagementSheet into
  QueueListView plus QueuePlaybackActions, reused by the tvOS panel.
- Remove the no-op wideLayoutPanelAlignment accessor from the settings
  view model; the player reads the panel side from SettingsManager, so
  the preset field was dead. The Codable field stays so old presets
  decode.
2026-09-21 23:04:42 +02:00
Arkadiusz Fal
6efd60a18e Play direct media links without Yattee Server by probing the URL first
Debrid and CDN download links (TorBox, Real-Debrid, presigned S3) hide the
file behind an opaque path, so the extension-based direct media check missed
them and every one was sent to Yattee Server for yt-dlp extraction, failing
outright when no server is configured.

Add DirectMediaProbe: a HEAD request classifies the response by Content-Type
and Content-Disposition, and when the type is generic or HEAD is refused, a
ranged GET sniffs the first bytes for container signatures (Matroska/WebM,
ISO BMFF, Ogg, FLAC, MP3, RIFF, FLV, ASF, MPEG-PS/TS, HLS and DASH
manifests). Anything playable goes straight to mpv with the server-reported
filename as the title; everything else falls back to extraction as before.

The probe runs ahead of the Yattee Server path in ExternalVideoView, both
OpenLinkSheet flows and the deep-link download path, and PlayerService
rebuilds direct_media streams itself so replays from history no longer
depend on a server either.
2026-09-21 20:03:06 +02:00
Arkadiusz Fal
5651f8d431 Add a toolbar layout for the video info actions with a configurable primary action
Layout & Navigation gains a Video Info section on iOS/macOS: the actions
(Play, Download, Share, Add to Playlist, Bookmark) can stay as inline
button rows or move to the navigation bar, where the chosen primary
action is pinned and the rest live in an ellipsis menu. The toolbar
layout with Download pinned is the new default, since the hero thumbnail
already plays the video on tap; a one-time TipKit popover on the
thumbnail points that out. tvOS keeps the existing button layout.
2026-09-20 23:52:20 +02:00
Arkadiusz Fal
1ac35f2273 Add Remote Servers and File Sources sections to Home
Two new configurable Home sections list enabled sources the way the
Sources screen does; tapping a remote server opens its browse view and
tapping a file source opens its file browser. A file source with missing
credentials shows the auth-required badge and routes to the Sources
screen instead. Both sections are hidden by default.
2026-09-20 23:45:19 +02:00
Arkadiusz Fal
c03df4777e Rework the video info header for full-width, leading-aligned layout
The thumbnail now spans the card width (capped at 640pt on wide
screens) instead of a fixed 240/280pt, and title, channel row, and
action buttons are leading-aligned into a single column. The title
always reserves two lines so carousel cards stay vertically stable
while swiping.

Thumbnails are sized explicitly from the container width because the
carousel's LazyHStack proposes its ideal height back to the cards;
that ideal follows the loaded bitmap's native size, so a low-quality
fallback thumbnail would otherwise shrink its card.
2026-09-20 23:40:16 +02:00
Arkadiusz Fal
eac5eb7778 Surface video details load failures in the info view
A failed details fetch for a video opened with list metadata set the
error state but never rendered it - the spinner cleared and the
description section silently disappeared. Show an inline error section
with a retry button and an error details sheet (copy/share, reusing the
player's ErrorDetailsSheet). Also fill in the empty video.error.title
localization.
2026-09-20 23:00:03 +02:00
Arkadiusz Fal
794b2b0181 Let accounts be set inactive without signing them out
An inactive account keeps its credential and syncs like a rename, but
drops out of every picker and cannot be used until activated again.
Deactivating the selected account switches to the local context as a
published choice, so other devices follow instead of falling back.
2026-09-19 23:58:24 +02:00
Arkadiusz Fal
01ff78aaa6 Keep destination checkmarks in sync with the main subscribe button
The main button's subscribe/unsubscribe never told the destination
membership loader, so "Subscribe On…" kept a stale checkmark for the
current account — and tapping that row would do the opposite of what
the mark promised.

Also: the menu item and success toast now name the account the write
lands on when more than one destination is offered, and "Subscribe On…"
no longer waits for the channel request — the cached header stands in
until the load answers.
2026-09-19 23:51:19 +02:00
Arkadiusz Fal
7db03f2392 Restore tvOS focus after removing a Continue Watching entry
Marking a video as watched (or removing it) from the context menu left
nothing focused once the entry disappeared. Track the last focused video
and move focus to the entry that took its place (or the previous one if
it was last). The context menu dismissal restores focus to the removed
row after the first attempt, so the target is re-asserted a few times
until the focus engine takes it.
2026-09-19 23:43:00 +02:00
Arkadiusz Fal
7c71c2d7b9 Use a solid fill for focused tvOS player controls
Materials render nearly transparent over the video layer on Apple TV HD,
making the material-only focus difference invisible. Focused buttons now
get a solid white fill with a black icon, AVKit-style.
2026-09-19 23:37:18 +02:00
Arkadiusz Fal
1353fc26d7 Force dark color scheme on the tvOS player
The player and its overlays (details panel, quality/queue panels,
error cover) always sit over dark video and hardcode white text, but
system controls — the Info/Comments segmented picker and the View
Channel bordered button — followed the device color scheme, rendering
near-invisible dark-on-dark under the light scheme.
2026-09-19 23:08:58 +02:00
Arkadiusz Fal
de8ed91029 Use a scheme-adaptive background for tvOS full screen covers
tvOS full screen covers are transparent, so each one filled itself with
a hardcoded black — invisible black-on-black text under the light color
scheme (e.g. the View All Comments page). A shared
tvFullScreenCoverBackground() modifier now picks black or light gray
from the color scheme; applied to the comments cover, subscription
destination picker, instance login, edit source, iCloud progress, and
legacy accounts import covers.
2026-09-19 23:04:01 +02:00
Arkadiusz Fal
40b906167c Fix white text in video info view under the light tvOS color scheme
The video title and TVScrollableDescription hardcoded white text, which
was unreadable on light backgrounds. Description colors now adapt to the
color scheme by default; the player details panel opts back into white
via overlaysDarkBackground since it always sits over a dark backdrop.
2026-09-18 23:59:57 +02:00
Arkadiusz Fal
5cd8d586c6 Add build date to the About settings view 2026-09-18 23:59:29 +02:00
Arkadiusz Fal
d22abf6088 Add an option to share videos via privacy frontend URLs
When enabled (the default), share links to YouTube videos point to the
active source's web watch page instead of youtube.com. Invidious serves
its frontend at the API URL; Piped uses its configured frontend URL.
Videos the active source can't serve (PeerTube, extracted sources) and
sources without a web frontend keep their original links.

The toggle lives in Settings > Layout & Navigation > Sharing on iOS and
macOS, and is shown only when an enabled source has a web watch page.
2026-09-18 23:57:34 +02:00
Arkadiusz Fal
172d712f25 Add an optional frontend URL for Piped instances used by browser actions 2026-09-18 23:36:11 +02:00
Arkadiusz Fal
045e0ba574 Add refresh, accounts, and link actions to the instance browse menu
The three-dots menu now offers Refresh, an accounts entry (sign in when
nobody is, otherwise a submenu that switches the data context, adds a
login, or signs out the active account with the shared removal
confirmation), and Copy URL / Open in Browser on iOS and macOS.
Login and sign-out reuse InstanceLoginView, AccountSignInHandler, and
the same confirmation strings as the edit sheet's accounts section.
2026-09-17 23:59:37 +02:00
Arkadiusz Fal
d742fec41a Replace the instance browse view options button with a three-dots menu
The toolbar (and tvOS inline) button now opens a menu with Edit and
Remove actions for the browsed instance above the existing View Options
entry. Edit opens the shared EditSourceView; Remove confirms with the
same account-deletion disclosure as Settings and pops the view.
2026-09-17 23:59:32 +02:00
Arkadiusz Fal
2a816bd893 Move basic auth credentials into the Security section in the instance edit form 2026-09-17 23:59:28 +02:00
Arkadiusz Fal
93886b20ea Surface video detail load failures in the error view for placeholder videos
Opening a members-only video from a history or feed row got a 500 from
the videos API and left the detail view blank: the retry error view only
covered the videoID init mode, while the details fetch for a loaded
video failed silently.

Record the details-fetch error and route it into the same error view
when the displayed video is a bare placeholder (no author, no
thumbnails), show a spinner while that fetch is in flight, and make
retry re-run both loaders. Videos with real partial metadata keep
rendering as before.
2026-09-17 23:34:15 +02:00
Arkadiusz Fal
101880d35f Route instance subscription imports through SubscriptionService with a destination picker
ImportSubscriptionsView wrote every imported channel straight into the
local SwiftData store via dataManager.subscribe(to:), regardless of the
selected account context. Imports now go through
SubscriptionService.subscribe(to:in:) aimed at a destination the user
picks — the local store or any logged-in account that supports
subscriptions, minus the source instance's own account, the same roster
the subscribe menus offer.

The already-subscribed marks are asked per destination through a new
SubscriptionService.subscribedChannelIDs(in:) helper (cold server
destinations fetch their list once, generation-fenced like the other
reads) and reload when the destination changes. Individual adds show a
per-row spinner while the server write is in flight, alreadySubscribed
counts as success, and Add All runs serially with an added/failed tally.

Also drop the "Invidious" wording from the import empty state — the
view serves Piped sources too.
2026-09-17 23:23:56 +02:00
Arkadiusz Fal
3e623cba2f Speak title, channel, and duration as the video row VoiceOver label
The row's accessibilityLabel was the debug string video.row.<id>, so
VoiceOver read a raw YouTube ID for every row. Build the label from the
DeArrow-aware display title, author name, and duration instead; the
accessibilityIdentifier stays unchanged for UI tests.
2026-09-17 19:18:41 +02:00
Arkadiusz Fal
b413648ab9 Hold the Accounts settings entry until a source can hold an account 2026-09-16 22:45:41 +02:00
Arkadiusz Fal
f6fde48767 Rebuild the sidebar switcher menu when the context name changes 2026-09-16 21:14:22 +02:00
Arkadiusz Fal
59c4f27bc7 Slim the iPad sidebar switcher pill to icon and account name 2026-09-16 18:27:17 +02:00
Arkadiusz Fal
5d33e4a558 Hide toolbar account switchers where the sidebar already shows one 2026-09-16 18:16:10 +02:00
Arkadiusz Fal
a5ed91620f Back the iPad sidebar switcher with its own pill 2026-09-15 23:57:59 +02:00
Arkadiusz Fal
0aa79221f4 Pin the account switcher under the iPad sidebar like macOS 2026-09-15 23:56:46 +02:00
Arkadiusz Fal
e28ca98831 Give the tvOS New Playlist row air between icon and title
A tvOS Label sets its plus glyph nearly against the text at this size;
the row spells the gap out with an HStack there, shared by the local and
remote sections. Other platforms keep the plain Label.
2026-09-15 23:23:47 +02:00
Arkadiusz Fal
ba553162f4 Name the tvOS destination picker in its row
A tvOS menu picker shows only its value, so the collapsed control read
as a bare account name. The menu's label is now the whole labelled row -
'Account' leading, the selection trailing, the same shape as the
settings switcher row - with the choices as a checkmarked picker inside
the menu. Other platforms keep the plain menu picker, which already
carries its label.
2026-09-15 22:56:10 +02:00
Arkadiusz Fal
62f7e3eeb3 Let the playlist selector's account picker collapse on tvOS
The tvOS branch pinned the picker to .inline over a fear that a menu
would dismiss the sheet it sits in — that is true of the hardware Menu
button and a fullScreenCover, not of a menu-style picker in a sheet. The
inline rows meanwhile filled the card with every account before the
first playlist. One .menu picker on every platform, device-verified:
picking another account swaps the section in place.
2026-09-15 18:03:04 +02:00
Arkadiusz Fal
753cda9661 Label the local context by where its data actually lives
"Yattee (iCloud)" was hardcoded for the local data context even with
iCloud sync disabled, when the data is purely on-device. The name now
comes from DataContextValidator.localContextName, which checks
iCloudSyncEnabled and falls back to "Yattee (This Device)".
2026-09-15 09:02:18 +02:00
Arkadiusz Fal
50e32dbe5a Make every tvOS switcher a menu and retire the full-screen picker
The header button and the settings row both raised a fullScreenCover to
pick a context; a Menu offers the same rows in place for one click,
matching the pickers elsewhere in Settings. With every presentation now
a menu, the picker view and the rows' inMenu flag had nothing left to
serve. The subscriptions header also cedes its width to the account
name: the view-options button keeps only its icon, since two full
titles wrapped into tall two-line lozenges in the tvOS sidebar.
2026-09-14 18:59:00 +02:00
Arkadiusz Fal
ded53d7962 Fold the tvOS account actions into a press-raised dialog
Each account was four focusable rows tall on tvOS — Use this account,
Sign in again, Rename and Sign out all spelled out as siblings, a lot
of clicking past on a remote. The row itself is now the one focusable
unit: pressing it raises the same actions as a confirmation dialog,
mirroring the context menu the other platforms already attach.
2026-09-14 18:28:28 +02:00
Arkadiusz Fal
a28346148a Show the tvOS channels sidebar for a single-channel account
The sidebar hid itself below two channels, so an account with one
subscription lost the channel list, the manage-channels entry and the
switcher's persistent home next to View Options - and switching from a
many-channel account read as the sidebar breaking rather than a design
choice. One channel is enough to earn the sidebar; only an empty account
goes without it.
2026-09-14 09:01:26 +02:00
Arkadiusz Fal
82ef5267c4 Let the sidebar's channels and playlists follow the data context 2026-09-14 07:11:28 +02:00
Arkadiusz Fal
f2c4721e5a Leave the macOS toolbars to the sidebar's account switcher 2026-09-13 20:38:26 +02:00
Arkadiusz Fal
69d22cdc98 Withhold every account switcher until an account exists to offer 2026-09-13 20:16:14 +02:00
Arkadiusz Fal
5136b79f49 Let the user hide the account pickers outside Settings 2026-09-13 18:56:18 +02:00
Arkadiusz Fal
ce97aa1031 Let the tapped playlist row spin while its add is on the wire
The in-flight gate now remembers which row it closed for, and that row
trades its plus for a small spinner until the checkmark - or the error
toast - takes over. Local adds are instant and keep going straight to
the check.
2026-09-13 14:01:06 +02:00
Arkadiusz Fal
80ed81b0e4 Let the playlist selector accept only one add per presentation
The tapped row stays on screen for the half second the checkmark shows -
and for a remote add, the whole round-trip - so quick repeated taps each
started another add and the video landed in the playlist several times.
One gate covers both paths; a failed remote add reopens it, since
failing is the one way the sheet stays up to retry from.
2026-09-13 13:48:25 +02:00
Arkadiusz Fal
a742aee353 Let the playlist selector show one destination at a time
A picker at the top - seeded from the selected data context, private to
the sheet from then on - replaces the stacked per-account sections, so
picking where a video goes no longer scrolls past every login and never
switches what the app is browsing as.
2026-09-13 08:07:06 +02:00
Arkadiusz Fal
b8921387dd Let the accounts screen own the switcher instead of the settings list
The main settings list carried both an Accounts row and a switcher row
beside it — two account controls at the top level, one of them opening
the screen the other belonged on. The switcher row moves to the top of
the accounts screen as a Current Account section, where the selection
it names sits directly above the checkmarked row it corresponds to.

The subscriptions-settings row and the macOS sidebar footer keep
theirs: those are contextual surfaces, not the settings list.
2026-09-12 23:58:32 +02:00
Arkadiusz Fal
a04b44e207 Let Home carry the account switcher and follow the selected account
The Home tab showed the same device-local numbers whatever data context
was selected, with no way to switch from there. Now it carries the same
DataContextSwitcherMenu as Subscriptions, History and Playlists, and its
data follows the switch:

- The history card and recent-history section resolve by the same
  HistoryDisplayMode rule as the history screen: an account with
  server-side history gets one probe of it (count up to 500, shown as a
  floor past that) and hydrated leading rows; a failed or still-out
  fetch reads as an em dash, never as the device's numbers under an
  account's name. Local writes no longer refetch the server; a posted
  watch does.
- The playlists card reads the remote playlists cache live in an
  account context, kicking the first fetch when the playlists screen
  never did.
- Context, credential and status changes reload the counts and refill
  the feed section the cache clearing just emptied; the channels-count
  fetch drops its result if the context moved while it was out.

Bookmarks, continue watching and downloads stay device-local on
purpose: they have no server counterpart. The notification
subscriptions moved into a dataObservers helper to keep the body within
the type-checker's budget.
2026-09-12 23:50:15 +02:00
Arkadiusz Fal
839de238d3 Let account labels call the source by its title, not its host
An account with no user-set label was named username@host everywhere it
appears — the switcher menu, the settings rows, the playlist selector —
which overflows a menu row the moment the host has a subdomain. The
default name and the switcher's disambiguation suffixes now use the
instance's title when one is set, falling back to the host as before.
2026-09-12 23:44:35 +02:00