mirror of
https://github.com/yattee/yattee.git
synced 2026-05-13 10:55:03 +00:00
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.
39 lines
1.7 KiB
Swift
39 lines
1.7 KiB
Swift
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
|
|
}
|
|
}
|