| 1 | // |
| 2 | // StreamingHomeViewController.swift |
| 3 | // JomloCollectionView 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 | @MainActor |
| 13 | final class StreamingHomeViewController: JomloCollectionViewController { |
| 14 | |
| 15 | init() { |
| 16 | super.init(sections: []) |
| 17 | } |
| 18 | |
| 19 | override func viewDidLoad() { |
| 20 | super.viewDidLoad() |
| 21 | title = "JomloCV" |
| 22 | #if os(iOS) |
| 23 | view.backgroundColor = .systemBackground |
| 24 | #endif |
| 25 | |
| 26 | // Register cells |
| 27 | collectionView.registerJomloCell(HeroCell.self) |
| 28 | collectionView.registerJomloCell(PosterCell.self) |
| 29 | collectionView.registerJomloCell(CategoryCell.self) |
| 30 | collectionView.registerJomloCell(ContinueWatchingCell.self) |
| 31 | |
| 32 | // Register header |
| 33 | collectionView.register( |
| 34 | JomloSectionHeaderView.self, |
| 35 | forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, |
| 36 | withReuseIdentifier: JomloSectionHeaderView.reuseIdentifier |
| 37 | ) |
| 38 | |
| 39 | // Load content |
| 40 | loadContent() |
| 41 | } |
| 42 | |
| 43 | private func loadContent() { |
| 44 | let sections: [any JomloSection] = [ |
| 45 | makeHeroSection(), |
| 46 | makeContinueWatchingSection(), |
| 47 | makeTrendingSection(), |
| 48 | makeSeriesSection(), |
| 49 | makeCategoriesSection(), |
| 50 | makeMoviesSection(), |
| 51 | ] |
| 52 | |
| 53 | setSections(sections, animated: true) |
| 54 | } |
| 55 | |
| 56 | // MARK: - Section Factories |
| 57 | |
| 58 | private func makeHeroSection() -> HeroSection { |
| 59 | HeroSection(items: [ |
| 60 | HeroItem( |
| 61 | id: "hero-1", |
| 62 | title: "The Last Kingdom", |
| 63 | subtitle: "Season 5 Now Streaming", |
| 64 | gradientColors: (top: "#1a237e", bottom: "#4a148c") |
| 65 | ), |
| 66 | HeroItem( |
| 67 | id: "hero-2", |
| 68 | title: "Formula 1: Drive to Survive", |
| 69 | subtitle: "New Season Available", |
| 70 | gradientColors: (top: "#b71c1c", bottom: "#e65100") |
| 71 | ), |
| 72 | HeroItem( |
| 73 | id: "hero-3", |
| 74 | title: "Premier League Live", |
| 75 | subtitle: "Every Match, Every Week", |
| 76 | gradientColors: (top: "#1b5e20", bottom: "#004d40") |
| 77 | ), |
| 78 | ]) |
| 79 | } |
| 80 | |
| 81 | private func makeContinueWatchingSection() -> ContinueWatchingSection { |
| 82 | ContinueWatchingSection(items: [ |
| 83 | ContinueWatchingItem( |
| 84 | id: "cw-1", title: "Breaking Bad", episodeInfo: "S3 E7 \u{00B7} One Minute", |
| 85 | progress: 0.65, colorHex: "#2e7d32"), |
| 86 | ContinueWatchingItem( |
| 87 | id: "cw-2", title: "The Bear", episodeInfo: "S2 E3 \u{00B7} Sundae", progress: 0.3, |
| 88 | colorHex: "#f57f17"), |
| 89 | ContinueWatchingItem( |
| 90 | id: "cw-3", title: "Succession", episodeInfo: "S4 E1 \u{00B7} The Munsters", |
| 91 | progress: 0.82, colorHex: "#1565c0"), |
| 92 | ContinueWatchingItem( |
| 93 | id: "cw-4", title: "Slow Horses", episodeInfo: "S3 E4 \u{00B7} Uninvited", |
| 94 | progress: 0.45, colorHex: "#6a1b9a"), |
| 95 | ]) |
| 96 | } |
| 97 | |
| 98 | private func makeTrendingSection() -> ShelfSection { |
| 99 | ShelfSection( |
| 100 | id: "trending", |
| 101 | title: "Trending Now", |
| 102 | items: [ |
| 103 | PosterItem(id: "t-1", title: "Oppenheimer", year: "2023", colorHex: "#ff6f00"), |
| 104 | PosterItem( |
| 105 | id: "t-2", title: "Killers of the Flower Moon", year: "2023", |
| 106 | colorHex: "#4e342e"), |
| 107 | PosterItem(id: "t-3", title: "Past Lives", year: "2023", colorHex: "#00838f"), |
| 108 | PosterItem( |
| 109 | id: "t-4", title: "Anatomy of a Fall", year: "2023", colorHex: "#283593"), |
| 110 | PosterItem(id: "t-5", title: "The Holdovers", year: "2023", colorHex: "#2e7d32"), |
| 111 | PosterItem(id: "t-6", title: "Poor Things", year: "2023", colorHex: "#ad1457"), |
| 112 | ], |
| 113 | presenter: self |
| 114 | ) |
| 115 | } |
| 116 | |
| 117 | private func makeSeriesSection() -> ShelfSection { |
| 118 | ShelfSection( |
| 119 | id: "series", |
| 120 | title: "Popular Series", |
| 121 | items: [ |
| 122 | PosterItem(id: "s-1", title: "The Bear", year: "2022\u{2013}", colorHex: "#e65100"), |
| 123 | PosterItem( |
| 124 | id: "s-2", title: "Slow Horses", year: "2022\u{2013}", colorHex: "#1a237e"), |
| 125 | PosterItem( |
| 126 | id: "s-3", title: "Severance", year: "2022\u{2013}", colorHex: "#004d40"), |
| 127 | PosterItem(id: "s-4", title: "Shogun", year: "2024", colorHex: "#b71c1c"), |
| 128 | PosterItem(id: "s-5", title: "True Detective", year: "2024", colorHex: "#311b92"), |
| 129 | PosterItem(id: "s-6", title: "Fallout", year: "2024", colorHex: "#33691e"), |
| 130 | ], |
| 131 | presenter: self |
| 132 | ) |
| 133 | } |
| 134 | |
| 135 | private func makeCategoriesSection() -> CategoriesGridSection { |
| 136 | CategoriesGridSection(items: [ |
| 137 | CategoryItem(id: "cat-1", name: "Movies", iconName: "\u{1F3AC}", colorHex: "#c62828"), |
| 138 | CategoryItem(id: "cat-2", name: "Series", iconName: "\u{1F4FA}", colorHex: "#ad1457"), |
| 139 | CategoryItem(id: "cat-3", name: "Sports", iconName: "\u{26BD}", colorHex: "#1565c0"), |
| 140 | CategoryItem(id: "cat-4", name: "Kids", iconName: "\u{1F9F8}", colorHex: "#2e7d32"), |
| 141 | CategoryItem( |
| 142 | id: "cat-5", name: "Documentaries", iconName: "\u{1F30D}", colorHex: "#e65100"), |
| 143 | CategoryItem(id: "cat-6", name: "Live TV", iconName: "\u{1F4E1}", colorHex: "#4527a0"), |
| 144 | ]) |
| 145 | } |
| 146 | |
| 147 | private func makeMoviesSection() -> ShelfSection { |
| 148 | ShelfSection( |
| 149 | id: "movies", |
| 150 | title: "New Movies", |
| 151 | items: [ |
| 152 | PosterItem(id: "m-1", title: "Dune: Part Two", year: "2024", colorHex: "#ff8f00"), |
| 153 | PosterItem(id: "m-2", title: "Civil War", year: "2024", colorHex: "#455a64"), |
| 154 | PosterItem(id: "m-3", title: "Challengers", year: "2024", colorHex: "#00695c"), |
| 155 | PosterItem( |
| 156 | id: "m-4", title: "The Zone of Interest", year: "2023", colorHex: "#37474f"), |
| 157 | PosterItem( |
| 158 | id: "m-5", title: "All of Us Strangers", year: "2023", colorHex: "#1a237e"), |
| 159 | ], |
| 160 | presenter: self |
| 161 | ) |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | // MARK: - UIColor Hex Extension |
| 166 | |
| 167 | extension UIColor { |
| 168 | convenience init?(hex: String) { |
| 169 | var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines) |
| 170 | hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "") |
| 171 | |
| 172 | var rgb: UInt64 = 0 |
| 173 | guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil } |
| 174 | |
| 175 | let r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0 |
| 176 | let g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0 |
| 177 | let b = CGFloat(rgb & 0x0000FF) / 255.0 |
| 178 | |
| 179 | self.init(red: r, green: g, blue: b, alpha: 1.0) |
| 180 | } |
| 181 | } |