mirror of
https://github.com/yattee/yattee.git
synced 2026-05-13 10:55:03 +00:00
Add tvOS Top Shelf extension
Surfaces Continue Watching, Recent Feed, and Recent Bookmarks in the
Apple TV Home top shelf when Yattee is focused. Tapping a tile opens
the video via the existing yattee://video/{id} deep link.
- New YatteeTopShelf app extension target (tvOS only). LD_ENTRY_POINT is
overridden to _NSExtensionMain; the tv-app-extension product type
defaults to _TVExtensionMain which is for the pre-tvOS-13 legacy API
and crashes modern TVTopShelfContentProvider subclasses at launch.
- Main app writes per-section JSON snapshots (capped at 10 items each)
to a shared App Group UserDefaults suite after bookmark, watch-history,
and feed-cache changes, plus an initial write on launch.
- Enabled-sections list is mirrored to the same App Group so the
extension can respect the user's selection without touching SwiftData.
- Settings → Top Shelf (tvOS only) lets the user toggle sections.
- Deep link playback shows a loading toast while video details are
fetched, and an error toast if no source is configured.
This commit is contained in:
38
YatteeTopShelf/TopShelfContentProvider.swift
Normal file
38
YatteeTopShelf/TopShelfContentProvider.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
import TVServices
|
||||
|
||||
class TopShelfContentProvider: TVTopShelfContentProvider {
|
||||
override func loadTopShelfContent(completionHandler: @escaping (any TVTopShelfContent) -> Void) {
|
||||
let defaults = AppGroup.defaults
|
||||
let enabled = TopShelfSnapshot.enabledSections(from: defaults)
|
||||
|
||||
let collections: [TVTopShelfItemCollection<TVTopShelfSectionedItem>] = enabled.compactMap { section in
|
||||
let items = TopShelfSnapshot.read(section: section, from: defaults)
|
||||
guard !items.isEmpty else { return nil }
|
||||
let sectioned = items.map { makeItem(from: $0, in: section) }
|
||||
let collection = TVTopShelfItemCollection(items: sectioned)
|
||||
collection.title = section.localizedTitle
|
||||
return collection
|
||||
}
|
||||
|
||||
completionHandler(TVTopShelfSectionedContent(sections: collections))
|
||||
}
|
||||
|
||||
private func makeItem(from item: TopShelfItem, in section: TopShelfSection) -> TVTopShelfSectionedItem {
|
||||
let sectioned = TVTopShelfSectionedItem(identifier: "\(section.rawValue).\(item.videoID)")
|
||||
sectioned.title = item.title
|
||||
sectioned.imageShape = .hdtv
|
||||
if let url = item.thumbnailURL.flatMap(URL.init(string:)) {
|
||||
sectioned.setImageURL(url, for: .screenScale1x)
|
||||
sectioned.setImageURL(url, for: .screenScale2x)
|
||||
}
|
||||
if let deepLink = URL(string: item.deepLinkURL) {
|
||||
sectioned.displayAction = TVTopShelfAction(url: deepLink)
|
||||
sectioned.playAction = TVTopShelfAction(url: deepLink)
|
||||
}
|
||||
if section == .continueWatching,
|
||||
let progress = item.progressSeconds, item.duration > 0 {
|
||||
sectioned.playbackProgress = max(0, min(1, progress / item.duration))
|
||||
}
|
||||
return sectioned
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user