| 1 | import Foundation |
| 2 | @preconcurrency import TVServices |
| 3 | |
| 4 | final class ContentProvider: TVTopShelfContentProvider { |
| 5 | override func loadTopShelfContent() async -> (any TVTopShelfContent)? { |
| 6 | var carouselItems: [TVTopShelfCarouselItem] = [] |
| 7 | |
| 8 | for item in TopShelfCatalog.items { |
| 9 | let carouselItem = TVTopShelfCarouselItem(identifier: item.id) |
| 10 | carouselItem.title = item.title |
| 11 | carouselItem.contextTitle = item.contextTitle |
| 12 | carouselItem.summary = item.summary |
| 13 | carouselItem.genre = item.kindTitle |
| 14 | carouselItem.namedAttributes = [ |
| 15 | TVTopShelfNamedAttribute(name: "Section", values: [item.sectionTitle]), |
| 16 | TVTopShelfNamedAttribute(name: "Type", values: [item.kindTitle]), |
| 17 | ] |
| 18 | carouselItem.displayAction = TVTopShelfAction(url: item.displayURL) |
| 19 | carouselItem.playAction = TVTopShelfAction(url: item.playURL) |
| 20 | |
| 21 | if let imageURL = Bundle.main.url(forResource: item.artworkName, withExtension: "png") { |
| 22 | carouselItem.setImageURL(imageURL, for: .screenScale1x) |
| 23 | carouselItem.setImageURL(imageURL, for: .screenScale2x) |
| 24 | } |
| 25 | |
| 26 | carouselItems.append(carouselItem) |
| 27 | } |
| 28 | |
| 29 | return TVTopShelfCarouselContent(style: .details, items: carouselItems) |
| 30 | } |
| 31 | } |
| 32 | |
| 33 | private enum TopShelfCatalog { |
| 34 | static let items: [Item] = [ |
| 35 | Item( |
| 36 | id: "klepon", |
| 37 | title: "Klepon", |
| 38 | contextTitle: "Featured sweet", |
| 39 | summary: |
| 40 | "A chewy rice cake with molten palm sugar and soft coconut that feels instantly comforting.", |
| 41 | kindTitle: "Dish", |
| 42 | sectionTitle: "Discover", |
| 43 | artworkName: "topshelf-klepon", |
| 44 | displayURL: URL(string: "klepon://entry?id=klepon")!, |
| 45 | playURL: URL(string: "klepon://ask?id=klepon")! |
| 46 | ), |
| 47 | Item( |
| 48 | id: "rendang", |
| 49 | title: "Rendang", |
| 50 | contextTitle: "Deep and celebratory", |
| 51 | summary: |
| 52 | "Slow-cooked beef, coconut, and spice reduced into one of Indonesia's richest classics.", |
| 53 | kindTitle: "Dish", |
| 54 | sectionTitle: "Discover", |
| 55 | artworkName: "topshelf-rendang", |
| 56 | displayURL: URL(string: "klepon://entry?id=rendang")!, |
| 57 | playURL: URL(string: "klepon://ask?id=rendang")! |
| 58 | ), |
| 59 | Item( |
| 60 | id: "sambal", |
| 61 | title: "Sambal", |
| 62 | contextTitle: "Pantry essential", |
| 63 | summary: |
| 64 | "A whole family of chile condiments that can be bright, smoky, sweet, sharp, or deeply savory.", |
| 65 | kindTitle: "Ingredient", |
| 66 | sectionTitle: "Discover", |
| 67 | artworkName: "topshelf-sambal", |
| 68 | displayURL: URL(string: "klepon://entry?id=sambal")!, |
| 69 | playURL: URL(string: "klepon://ask?id=sambal")! |
| 70 | ), |
| 71 | Item( |
| 72 | id: "street-food", |
| 73 | title: "Street food culture", |
| 74 | contextTitle: "Daily food rhythm", |
| 75 | summary: |
| 76 | "Portable meals, market snacks, and fast comfort that shape how many people first meet Indonesian food.", |
| 77 | kindTitle: "Tradition", |
| 78 | sectionTitle: "Discover", |
| 79 | artworkName: "topshelf-street-food", |
| 80 | displayURL: URL(string: "klepon://entry?id=street-food")!, |
| 81 | playURL: URL(string: "klepon://ask?id=street-food")! |
| 82 | ), |
| 83 | ] |
| 84 | |
| 85 | struct Item { |
| 86 | let id: String |
| 87 | let title: String |
| 88 | let contextTitle: String |
| 89 | let summary: String |
| 90 | let kindTitle: String |
| 91 | let sectionTitle: String |
| 92 | let artworkName: String |
| 93 | let displayURL: URL |
| 94 | let playURL: URL |
| 95 | } |
| 96 | } |