main
swift 246 lines 10.2 KB
Raw
1 //
2 // TVStreamingHomeViewController.swift
3 // JomloCV tvOS Example
4 //
5 // Created by Seto Elkahfi on 2026-01-27.
6 // Copyright © 2026 Seto Elkahfi. All rights reserved.
7 //
8
9 import JomloCollectionView
10 import UIKit
11
12 /// The main view controller for the tvOS example app.
13 ///
14 /// Demonstrates a streaming-service home screen optimized for Apple TV:
15 /// - Full-width hero section with group-paging for Siri Remote swipes
16 /// - "Continue Watching" shelf with progress indicators
17 /// - Multiple content shelves with large poster cards
18 /// - Category navigation row
19 /// - All cells use the tvOS focus engine with scale + shadow animations
20 @MainActor
21 final class TVStreamingHomeViewController: JomloCollectionViewController {
22
23 init() {
24 super.init(sections: [])
25 }
26
27 override func viewDidLoad() {
28 super.viewDidLoad()
29 view.backgroundColor = UIColor(white: 0.05, alpha: 1.0)
30
31 // Register TV-optimized cells
32 collectionView.registerJomloCell(TVHeroCell.self)
33 collectionView.registerJomloCell(TVPosterCell.self)
34 collectionView.registerJomloCell(TVSeeAllCell.self)
35 collectionView.registerJomloCell(TVCategoryCell.self)
36 collectionView.registerJomloCell(TVResumeCell.self)
37
38 // Register TV section header
39 collectionView.register(
40 TVSectionHeaderView.self,
41 forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
42 withReuseIdentifier: TVSectionHeaderView.reuseIdentifier
43 )
44
45 // Ensure cells can receive focus outside visible bounds during scrolling
46 collectionView.clipsToBounds = false
47 collectionView.remembersLastFocusedIndexPath = true
48
49 loadContent()
50 }
51
52 // MARK: - Content Assembly
53
54 private func loadContent() {
55 let sections: [any JomloSection] = [
56 makeHeroSection(),
57 makeResumeSection(),
58 makeTrendingShelf(),
59 makeCategorySection(),
60 makeSeriesShelf(),
61 makeMoviesShelf(),
62 makeSportsShelf(),
63 ]
64 setSections(sections, animated: false)
65 }
66
67 // MARK: - Section Factories
68
69 private func makeHeroSection() -> TVHeroSection {
70 TVHeroSection(items: [
71 TVHeroItem(
72 id: "hero-1",
73 title: "The Last Kingdom",
74 tagline: "The epic saga concludes",
75 metadata: "Season 5 \u{2022} Drama \u{2022} 2022",
76 gradientTop: "#1a237e",
77 gradientBottom: "#0d47a1"
78 ),
79 TVHeroItem(
80 id: "hero-2",
81 title: "Formula 1:\nDrive to Survive",
82 tagline: "Every race. Every rivalry.",
83 metadata: "Season 6 \u{2022} Documentary \u{2022} 2024",
84 gradientTop: "#b71c1c",
85 gradientBottom: "#880e4f"
86 ),
87 TVHeroItem(
88 id: "hero-3",
89 title: "Premier League",
90 tagline: "Every match. Every week. Live.",
91 metadata: "Live Sport \u{2022} Football \u{2022} 2024-25",
92 gradientTop: "#1b5e20",
93 gradientBottom: "#004d40"
94 ),
95 TVHeroItem(
96 id: "hero-4",
97 title: "Dune: Part Two",
98 tagline: "The legend continues",
99 metadata: "2024 \u{2022} Sci-Fi \u{2022} 2h 46m",
100 gradientTop: "#e65100",
101 gradientBottom: "#bf360c"
102 ),
103 ])
104 }
105
106 private func makeResumeSection() -> TVResumeSection {
107 TVResumeSection(items: [
108 TVResumeItem(
109 id: "r-1", title: "Breaking Bad", episodeInfo: "S3 E7 \u{2022} One Minute",
110 progress: 0.65, colorHex: "#2e7d32", remainingTime: "18 min left"),
111 TVResumeItem(
112 id: "r-2", title: "The Bear", episodeInfo: "S2 E3 \u{2022} Sundae", progress: 0.3,
113 colorHex: "#f57f17", remainingTime: "24 min left"),
114 TVResumeItem(
115 id: "r-3", title: "Succession", episodeInfo: "S4 E1 \u{2022} The Munsters",
116 progress: 0.82, colorHex: "#1565c0", remainingTime: "11 min left"),
117 TVResumeItem(
118 id: "r-4", title: "Slow Horses", episodeInfo: "S3 E4 \u{2022} Uninvited",
119 progress: 0.45, colorHex: "#6a1b9a", remainingTime: "28 min left"),
120 TVResumeItem(
121 id: "r-5", title: "True Detective", episodeInfo: "S4 E2 \u{2022} Night Country",
122 progress: 0.15, colorHex: "#37474f", remainingTime: "52 min left"),
123 ])
124 }
125
126 private func makeTrendingShelf() -> TVShelfSection {
127 TVShelfSection(
128 id: "trending",
129 title: "Trending Now",
130 items: [
131 TVContentItem(
132 id: "t-1", title: "Oppenheimer", subtitle: "2023 \u{2022} Drama",
133 colorHex: "#ff6f00", isFeatured: true),
134 TVContentItem(
135 id: "t-2", title: "Killers of the\nFlower Moon",
136 subtitle: "2023 \u{2022} Crime", colorHex: "#4e342e", isFeatured: false),
137 TVContentItem(
138 id: "t-3", title: "Past Lives", subtitle: "2023 \u{2022} Romance",
139 colorHex: "#00838f", isFeatured: false),
140 TVContentItem(
141 id: "t-4", title: "Anatomy\nof a Fall", subtitle: "2023 \u{2022} Thriller",
142 colorHex: "#283593", isFeatured: false),
143 TVContentItem(
144 id: "t-5", title: "The Holdovers", subtitle: "2023 \u{2022} Comedy",
145 colorHex: "#2e7d32", isFeatured: false),
146 TVContentItem(
147 id: "t-6", title: "Poor Things", subtitle: "2023 \u{2022} Fantasy",
148 colorHex: "#ad1457", isFeatured: true),
149 TVContentItem(
150 id: "t-7", title: "The Zone\nof Interest", subtitle: "2023 \u{2022} Drama",
151 colorHex: "#37474f", isFeatured: false),
152 ],
153 presenter: self
154 )
155 }
156
157 private func makeCategorySection() -> TVCategorySection {
158 TVCategorySection(items: [
159 TVCategoryItem(id: "cat-1", name: "Movies", icon: "\u{1F3AC}", colorHex: "#c62828"),
160 TVCategoryItem(id: "cat-2", name: "Series", icon: "\u{1F4FA}", colorHex: "#6a1b9a"),
161 TVCategoryItem(id: "cat-3", name: "Sport", icon: "\u{26BD}", colorHex: "#1565c0"),
162 TVCategoryItem(id: "cat-4", name: "Kids", icon: "\u{1F9F8}", colorHex: "#2e7d32"),
163 TVCategoryItem(
164 id: "cat-5", name: "Documentaries", icon: "\u{1F30D}", colorHex: "#e65100"),
165 TVCategoryItem(id: "cat-6", name: "Live TV", icon: "\u{1F4E1}", colorHex: "#283593"),
166 ])
167 }
168
169 private func makeSeriesShelf() -> TVShelfSection {
170 TVShelfSection(
171 id: "series",
172 title: "Popular Series",
173 items: [
174 TVContentItem(
175 id: "s-1", title: "The Bear", subtitle: "2022\u{2013} \u{2022} Comedy",
176 colorHex: "#e65100", isFeatured: true),
177 TVContentItem(
178 id: "s-2", title: "Slow Horses", subtitle: "2022\u{2013} \u{2022} Spy",
179 colorHex: "#1a237e", isFeatured: false),
180 TVContentItem(
181 id: "s-3", title: "Severance", subtitle: "2022\u{2013} \u{2022} Sci-Fi",
182 colorHex: "#004d40", isFeatured: false),
183 TVContentItem(
184 id: "s-4", title: "Shogun", subtitle: "2024 \u{2022} Historical",
185 colorHex: "#b71c1c", isFeatured: true),
186 TVContentItem(
187 id: "s-5", title: "True Detective", subtitle: "2024 \u{2022} Crime",
188 colorHex: "#311b92", isFeatured: false),
189 TVContentItem(
190 id: "s-6", title: "Fallout", subtitle: "2024 \u{2022} Sci-Fi",
191 colorHex: "#33691e", isFeatured: false),
192 ],
193 presenter: self
194 )
195 }
196
197 private func makeMoviesShelf() -> TVShelfSection {
198 TVShelfSection(
199 id: "movies",
200 title: "New Releases",
201 items: [
202 TVContentItem(
203 id: "m-1", title: "Dune:\nPart Two", subtitle: "2024 \u{2022} Sci-Fi",
204 colorHex: "#ff8f00", isFeatured: true),
205 TVContentItem(
206 id: "m-2", title: "Civil War", subtitle: "2024 \u{2022} Action",
207 colorHex: "#455a64", isFeatured: false),
208 TVContentItem(
209 id: "m-3", title: "Challengers", subtitle: "2024 \u{2022} Drama",
210 colorHex: "#00695c", isFeatured: false),
211 TVContentItem(
212 id: "m-4", title: "Furiosa", subtitle: "2024 \u{2022} Action",
213 colorHex: "#795548", isFeatured: false),
214 TVContentItem(
215 id: "m-5", title: "Inside Out 2", subtitle: "2024 \u{2022} Animation",
216 colorHex: "#7b1fa2", isFeatured: true),
217 ],
218 presenter: self
219 )
220 }
221
222 private func makeSportsShelf() -> TVShelfSection {
223 TVShelfSection(
224 id: "sports",
225 title: "Live & Upcoming Sport",
226 items: [
227 TVContentItem(
228 id: "sp-1", title: "Premier\nLeague", subtitle: "Live \u{2022} Football",
229 colorHex: "#1a237e", isFeatured: true),
230 TVContentItem(
231 id: "sp-2", title: "Champions\nLeague", subtitle: "Tomorrow \u{2022} Football",
232 colorHex: "#01579b", isFeatured: false),
233 TVContentItem(
234 id: "sp-3", title: "Formula 1", subtitle: "Sunday \u{2022} Racing",
235 colorHex: "#b71c1c", isFeatured: true),
236 TVContentItem(
237 id: "sp-4", title: "Handball", subtitle: "Today \u{2022} Live",
238 colorHex: "#4a148c", isFeatured: false),
239 TVContentItem(
240 id: "sp-5", title: "NHL", subtitle: "Tonight \u{2022} Ice Hockey",
241 colorHex: "#1b5e20", isFeatured: false),
242 ],
243 presenter: self
244 )
245 }
246 }