首页开发完成

This commit is contained in:
2025-04-22 15:21:12 +08:00
parent 6dd3e19b8a
commit 092092e546
42 changed files with 1116 additions and 103 deletions

View File

@ -12,7 +12,7 @@ class SPTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let nav1 = createNavigationController(viewController: SPHomePageController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected"))
let nav1 = createNavigationController(viewController: SPHomeViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected"))
let nav2 = createNavigationController(viewController: SPExplorePageController(), title: "For You".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected"))

View File

@ -57,6 +57,16 @@ public func spLog(message:Any? , file: String = #file, function: String = #funct
#endif
}
//MARK:-------------- --------------
///
public func kSPAngleToRadians(angle: CGFloat) -> CGFloat {
return angle / 180.0 * CGFloat.pi
}
///
public func kSPRadiansToAngle(radians: CGFloat) -> CGFloat {
return radians * (180.0 / CGFloat.pi)
}
public func sp_swizzled_instanceMethod(_ prefix: String, oldClass: Swift.AnyClass!, oldSelector: String, newClass: Swift.AnyClass) {
let newSelector = prefix + "_" + oldSelector;

View File

@ -108,5 +108,21 @@ extension UIColor {
static func color181115(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0x181115, alpha: alpha)
}
static func colorFF5100(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0xFF5100, alpha: alpha)
}
static func colorAFAFAF(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0xAFAFAF, alpha: alpha)
}
static func colorA8A5AA(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0xA8A5AA, alpha: alpha)
}
static func colorD9D9D9(alpha: CGFloat = 1) -> UIColor {
return color(hex: 0xD9D9D9, alpha: alpha)
}
}

View File

@ -24,4 +24,8 @@ extension UIFont {
static func fontLight(ofSize: CGFloat) -> UIFont {
return .systemFont(ofSize: ofSize, weight: .light)
}
static func fontWeight(ofSize: CGFloat, weight: CGFloat) -> UIFont {
return .systemFont(ofSize: ofSize, weight: UIFont.Weight(weight))
}
}

View File

@ -37,8 +37,8 @@ class SPHomeAPI: NSObject {
///
static func requestHomeModuleData(completer: ((_ model: SPHomeModuleModel?) -> Void)?) {
var param = SPNetworkParameters(path: "/homeModuleData")
param.method = .get
let param = SPNetworkParameters(path: "/homeBannerAndNineSquare")
// param.method = .get
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPHomeModuleModel>) in
completer?(response.data)
@ -46,6 +46,19 @@ class SPHomeAPI: NSObject {
}
///
static func requestHomeList(page: Int , completer: ((_ listModel: SPListModel<SPShortModel>?) -> Void)?) {
var param = SPNetworkParameters(path: "/newShortPlay")
param.parameters = [
"page_size" : 20,
"current_page" : page
]
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPListModel<SPShortModel>>) in
completer?(response.data)
}
}
///
static func requestSearch(text: String, completer: ((_ list: [SPShortModel]?) -> Void)?) {
var param = SPNetworkParameters(path: "/search")

View File

@ -8,31 +8,27 @@
import UIKit
/*
https://api-moviatv.moviatv.com/93f03506/
https://api-mireotv.mireotv.com/4da6fd4c/
https://api-vibeoshort.vibeoshort.com/bf86d973/
https://api-viontv.viontv.com/b7afef99/
https://api-zyreotv.zyreotv.com/7834f11d/
https://admin-thimratv.guyantv.com/login
https://api-thimratv.thimratv.com
https://api-thimratv.thimratv.com/0a2c5b02/
app的web需要适配
https://thimratv.com/
https://www.thimratv.com/
https://campaign.thimratv.com/
*/
#if DEBUG
let SPBaseURL = "https://test1-api.guyantv.com"
//let SPBaseURL = "https://api-thimratv.thimratv.com"
let SPURLPathPrefix = ""
//let SPBaseURL = "https://api-mireotv.mireotv.com"
//let SPURLPathPrefix = "/4da6fd4c"
//let SPBaseURL = "https://api-thimratv.thimratv.com"
//let SPURLPathPrefix = "/0a2c5b02"
let SPWebBaseURL = "https://www.guyantv.com"
let SPWebBaseURL = "https://www.thimratv.com"
#else
let SPBaseURL = "https://test1-api.guyantv.com"
let SPURLPathPrefix = "/4da6fd4c"
let SPWebBaseURL = "https://www.guyantv.com"
let SPBaseURL = " https://api-thimratv.thimratv.com"
let SPURLPathPrefix = "/0a2c5b02"
let SPWebBaseURL = "https://www.thimratv.com"
#endif

View File

@ -0,0 +1,107 @@
//
// SPZoomCollectionViewLayout.swift
// Thimra
//
// Created by Overseas on 2025/4/22.
//
import UIKit
class SPZoomCollectionViewLayout: UICollectionViewFlowLayout {
let minimumScale: CGFloat = 0.08 //
private(set) var currentIndexPath: IndexPath = IndexPath(row: 0, section: 0)
private var cellWidth: CGFloat {
return itemSize.width + minimumLineSpacing
}
override func prepare() {
super.prepare()
scrollDirection = .horizontal
self.collectionView?.decelerationRate = .fast
let screenWidth = UIScreen.main.bounds.size.width
let insetLeft = (screenWidth - self.itemSize.width) / 2
collectionView?.contentInset = UIEdgeInsets(top: 0, left: insetLeft, bottom: 0, right: insetLeft)
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) }
let proposedRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.width, height: collectionView.bounds.height)
guard let layoutAttributes = layoutAttributesForElements(in: proposedRect) else {
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
}
let horizontalCenterX = proposedContentOffset.x + collectionView.bounds.width / 2
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
// ydLog(message: "offsetAdjustment = \(offsetAdjustment)")
// ydLog(message: "horizontalCenterX = \(horizontalCenterX)")
var currentIndexPath: IndexPath = IndexPath(row: 0, section: 0)
for attributes in layoutAttributes {
let itemHorizontalCenterX = attributes.center.x
let distance = itemHorizontalCenterX - horizontalCenterX
// ydLog(message: "distance = \(distance)")
//
if abs(distance) < abs(offsetAdjustment) {
offsetAdjustment = distance
currentIndexPath = attributes.indexPath
}
}
self.currentIndexPath = currentIndexPath
let point = CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
// ydLog(message: "proposedContentOffset = \(proposedContentOffset)")
// ydLog(message: "offsetAdjustment = \(offsetAdjustment)")
// ydLog(message: "point = \(point)")
// ydLog(message: "currentIndex = \(currentIndex)")
return point
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let collectionView = collectionView else { return nil }
let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let attributes = super.layoutAttributesForElements(in: rect)?.compactMap { $0.copy() as? UICollectionViewLayoutAttributes }
for attribute in attributes ?? [] {
let distance = visibleRect.midX - attribute.center.x
let normalizedDistance = distance / (collectionView.bounds.width * 0.5)
let zoom = 1 - abs(normalizedDistance) * minimumScale
//
let scaleTransform = CGAffineTransform(scaleX: zoom, y: zoom)
//
let rotationAngle = kSPAngleToRadians(angle: -normalizedDistance * 4)
let rotationTransform = CGAffineTransform(rotationAngle: rotationAngle)
//
let combinedTransform = rotationTransform.concatenating(scaleTransform)
//
attribute.transform = combinedTransform
var alpha = 1.8 - abs(normalizedDistance)
if alpha < 0 {
alpha = 0
} else if alpha > 1 {
alpha = 1
}
attribute.alpha = alpha
}
return attributes
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}

View File

@ -17,7 +17,7 @@ class SPHomeChildController: SPViewController {
}
override func setBgImageView() { }
// override func setBgImageView() { }

View File

@ -8,14 +8,37 @@
import UIKit
class SPHomeViewController: SPHomeChildController {
///
private var moduleModel: SPHomeModuleModel?
private lazy var viewModel: SPHomeViewModel = SPHomeViewModel()
private lazy var page = 1
private lazy var dataArr: [SPShortModel] = []
//MARK: UI
private lazy var logoImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "logo_icon_01"))
imageView.setContentHuggingPriority(.required, for: .horizontal)
imageView.setContentCompressionResistancePriority(.required, for: .horizontal)
return imageView
}()
private lazy var searchButton: SPHomeSearchButton = {
let button = SPHomeSearchButton()
button.addTarget(self, action: #selector(handleSearchButton), for: .touchUpInside)
return button
}()
private lazy var layout: UICollectionViewFlowLayout = {
let width = floor((kSPScreenWidth - 32 - 13) / 2)
let height = 221 / 165 * width + 44
let layout = UICollectionViewFlowLayout()
layout.headerReferenceSize = CGSize(width: kSPScreenWidth, height: 200)
layout.itemSize = CGSize(width: width, height: height)
layout.headerReferenceSize = CGSize(width: kSPScreenWidth, height: SPHomeHeaderView.contentHeight(viewModel: self.viewModel))
layout.sectionInset = .init(top: 0, left: 16, bottom: 0, right: 16)
layout.minimumInteritemSpacing = 13
layout.minimumLineSpacing = 13
return layout
}()
@ -23,23 +46,47 @@ class SPHomeViewController: SPHomeChildController {
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.sp_addRefreshHeader { [weak self] in
self?.handleHeaderRefresh(nil)
}
collectionView.sp_addRefreshBackFooter(insetBottom: 0) { [weak self] in
self?.handleFooterRefresh(nil)
}
collectionView.register(SPHomeHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "headerView")
SPCollectionViewCell.registerCell(collectionView: collectionView)
SPHomeShortCell.registerCell(collectionView: collectionView)
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(reachabilityDidChangeNotification), name: SPNetworkReachabilityManager.reachabilityDidChangeNotification, object: nil)
// view.backgroundColor = .clear
requestModuleData()
requestPlayHistory()
requestListDataArr(page: 1, completer: nil)
_setupUI()
}
override func fetchChildControllerScrollView() -> UIScrollView? {
return self.collectionView
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
requestModuleData()
requestPlayHistory()
requestListDataArr(page: 1) { [weak self] in
self?.collectionView.sp_endHeaderRefreshing()
}
}
override func handleFooterRefresh(_ completer: (() -> Void)?) {
requestListDataArr(page: self.page + 1) { [weak self] in
self?.collectionView.sp_endFooterRefreshing()
}
}
}
@ -47,42 +94,71 @@ class SPHomeViewController: SPHomeChildController {
extension SPHomeViewController {
private func _setupUI() {
view.addSubview(self.collectionView);
view.addSubview(logoImageView)
view.addSubview(searchButton)
view.addSubview(self.collectionView)
logoImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.centerY.equalTo(searchButton)
}
searchButton.snp.makeConstraints { make in
make.left.equalTo(logoImageView.snp.right).offset(6)
make.right.equalToSuperview().offset(-16)
make.top.equalToSuperview().offset(kSPStatusbarHeight + 10)
}
self.collectionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalToSuperview().offset(topMargins)
// make.top.equalToSuperview().offset(kSPStatusbarHeight + 66)
make.top.equalTo(searchButton.snp.bottom).offset(34)
}
}
}
extension SPHomeViewController {
@objc private func handleSearchButton() {
let vc = SPSearchViewController()
self.navigationController?.pushViewController(vc, animated: true)
}
@objc private func reachabilityDidChangeNotification() {
}
}
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
extension SPHomeViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = SPCollectionViewCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
let cell = SPHomeShortCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
cell.model = dataArr[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 0
return dataArr.count
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "headerView", for: indexPath) as? SPHomeHeaderView {
headerView.moduleModel = self.moduleModel
headerView.viewModel = self.viewModel
return headerView
}
return UICollectionReusableView()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = self.dataArr[indexPath.row]
let vc = SPPlayerDetailViewController()
vc.shortPlayId = model.short_play_id
self.navigationController?.pushViewController(vc, animated: true)
}
}
@ -93,11 +169,42 @@ extension SPHomeViewController {
SPHomeAPI.requestHomeModuleData { [weak self] model in
guard let self = self else { return }
if let model = model {
self.moduleModel = model
self.layout.headerReferenceSize = CGSize(width: kSPScreenWidth, height: SPHomeHeaderView.contentHeight(model: model))
self.viewModel.moduleModel = model
self.layout.headerReferenceSize = CGSize(width: kSPScreenWidth, height: SPHomeHeaderView.contentHeight(viewModel: self.viewModel))
self.collectionView.reloadData()
}
}
}
///
private func requestPlayHistory() {
SPVideoAPI.requestPlayHistoryList(page: 1) { [weak self] listModel in
guard let self = self else { return }
if let list = listModel?.list {
self.viewModel.playHistoryArr = list
self.layout.headerReferenceSize = CGSize(width: kSPScreenWidth, height: SPHomeHeaderView.contentHeight(viewModel: self.viewModel))
self.collectionView.reloadData()
}
}
}
///
private func requestListDataArr(page: Int, completer: (() -> Void)?) {
SPHomeAPI.requestHomeList(page: page) { [weak self] listModel in
guard let self = self else { return }
if let list = listModel?.list {
if page == 1 {
self.dataArr.removeAll()
}
self.dataArr += list
self.collectionView.reloadData()
self.page = page
}
completer?()
}
}
}

View File

@ -12,7 +12,7 @@ class SPHomeModuleModel: SPModel, SmartCodable {
var bannerData: [SPShortModel]?
///
var recommandData: [SPShortModel]?
var nineSquare: SPHomeNineSquareModel?
///
var manualNewestRecommand: [SPShortModel]?
///
@ -21,3 +21,9 @@ class SPHomeModuleModel: SPModel, SmartCodable {
}
class SPHomeNineSquareModel: SPModel, SmartCodable {
var list: [SPShortModel]?
var title: String?
}

View File

@ -10,7 +10,7 @@ import UIKit
class SPHomeDataItemView: UIView {
///
static let contentToTop: CGFloat = 36
static let contentToTop: CGFloat = 32
class func contentHeight(dataArr: [SPShortModel]) -> CGFloat {
@ -30,14 +30,19 @@ class SPHomeDataItemView: UIView {
private(set) lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 15)
label.font = .fontWeight(ofSize: 18, weight: 400)
label.textColor = .colorFFFFFF()
return label
}()
private(set) lazy var iconImageView: UIImageView = {
let imageView = UIImageView()
return imageView
}()
private lazy var moreButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("More", for: .normal)
button.setTitle("More".localized, for: .normal)
button.setTitleColor(.colorF564B6(), for: .normal)
button.titleLabel?.font = .fontLight(ofSize: 12)
return button
@ -64,20 +69,26 @@ extension SPHomeDataItemView {
private func _setupUI() {
addSubview(titleLabel)
addSubview(moreButton)
addSubview(iconImageView)
// addSubview(moreButton)
addSubview(contentView)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.left.equalToSuperview().offset(16)
make.top.equalToSuperview()
make.height.equalTo(21)
}
moreButton.snp.makeConstraints { make in
make.centerY.equalTo(moreButton)
make.right.equalToSuperview().offset(-15)
iconImageView.snp.makeConstraints { make in
make.centerY.equalTo(titleLabel)
make.left.equalTo(titleLabel.snp.right).offset(8)
}
// moreButton.snp.makeConstraints { make in
// make.centerY.equalTo(titleLabel)
// make.right.equalToSuperview().offset(-15)
// }
contentView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(Self.contentToTop)

View File

@ -0,0 +1,65 @@
//
// SPHomeExploreCell.swift
// Thimra
//
// Created by Overseas on 2025/4/22.
//
import UIKit
class SPHomeExploreCell: SPCollectionViewCell {
var model: SPShortModel? {
didSet {
coverImageView.sp_setImage(url: model?.image_url)
titleLabel.text = model?.name
}
}
//MARK: UI
private lazy var coverImageView: SPImageView = {
let imageView = SPImageView()
imageView.layer.cornerRadius = 4
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
label.textColor = .colorFFFFFF()
label.numberOfLines = 2
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeExploreCell {
private func _setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(titleLabel)
coverImageView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.bottom.equalToSuperview().offset(-36)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview()
make.right.lessThanOrEqualToSuperview()
make.top.equalTo(coverImageView.snp.bottom).offset(8)
}
}
}

View File

@ -0,0 +1,109 @@
//
// SPHomeExploreView.swift
// Thimra
//
// Created by Overseas on 2025/4/22.
//
import UIKit
class SPHomeExploreView: SPHomeDataItemView {
private static func itemSize() -> CGSize {
let width = floor((kSPScreenWidth - 16 * 2 - 14 * 2) / 3)
let imageScale: CGFloat = 146 / 105
let height = width * imageScale + 36
return CGSize(width: width, height: height)
}
override class func contentHeight(dataArr: [SPShortModel]) -> CGFloat {
var height = self.contentToTop
var lineCount = dataArr.count / 3
if dataArr.count % 3 > 0 {
lineCount += 1
}
let contentHeight = itemSize().height * CGFloat(lineCount) + 14 * CGFloat(lineCount - 1)
if contentHeight > 0 {
height += contentHeight
} else {
height += 1
}
return height
}
override var dataArr: [SPShortModel]? {
didSet {
self.collectionView.reloadData()
}
}
//MARK: UI
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = Self.itemSize()
layout.minimumInteritemSpacing = 14
layout.minimumLineSpacing = 14
layout.sectionInset = .init(top: 0, left: 16, bottom: 0, right: 16)
return layout
}()
private lazy var collectionView: SPCollectionView = {
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
SPHomeExploreCell.registerCell(collectionView: collectionView)
return collectionView
}()
override init(frame: CGRect) {
super.init(frame: frame)
titleLabel.text = "Explore For You".localized
iconImageView.image = UIImage(named: "mark_icon_01")
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeExploreView {
private func _setupUI() {
contentView.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
extension SPHomeExploreView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = SPHomeExploreCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
cell.model = dataArr?[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArr?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = self.dataArr?[indexPath.row]
let vc = SPPlayerDetailViewController()
vc.shortPlayId = model?.short_play_id
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}
}

View File

@ -9,27 +9,40 @@ import UIKit
class SPHomeHeaderView: UICollectionReusableView {
var moduleModel: SPHomeModuleModel? {
var viewModel: SPHomeViewModel? {
didSet {
let moduleModel = viewModel?.moduleModel
stackView.removeAllArrangedSubview()
stackView.addArrangedSubview(bannerView)
bannerView.reloadData()
// if (moduleModel?.recommandData?.count ?? 0) > 0 {
// }
stackView.addArrangedSubview(trendingView)
trendingView.dataArr = moduleModel?.bannerData
if let historyList = viewModel?.playHistoryArr, historyList.count > 0 {
stackView.addArrangedSubview(playHistoryView)
playHistoryView.dataArr = historyList
}
stackView.addArrangedSubview(hotView)
hotView.dataArr = moduleModel?.bannerData
stackView.addArrangedSubview(shortsForYouView)
shortsForYouView.dataArr = moduleModel?.bannerData
if let list = moduleModel?.nineSquare?.list, list.count > 0 {
stackView.addArrangedSubview(exploreView)
exploreView.titleLabel.text = moduleModel?.nineSquare?.title
exploreView.dataArr = list
}
// stackView.addArrangedSubview(trendingView)
// trendingView.dataArr = moduleModel?.bannerData
//
// stackView.addArrangedSubview(hotView)
// hotView.dataArr = moduleModel?.bannerData
//
// stackView.addArrangedSubview(shortsForYouView)
// shortsForYouView.dataArr = moduleModel?.bannerData
stackView.addArrangedSubview(titleView)
}
}
private lazy var stackView: UIStackView = {
let view = UIStackView()
view.axis = .vertical
@ -37,19 +50,37 @@ class SPHomeHeaderView: UICollectionReusableView {
return view
}()
private lazy var bannerView: ZKCycleScrollView = {
let bannerView = ZKCycleScrollView(frame: .zero, shouldInfiniteLoop: true);
bannerView.delegate = self
bannerView.dataSource = self
bannerView.itemSpacing = 10
bannerView.itemSize = CGSize(width: kSPScreenWidth - 30, height: Self.bannerHeight())
bannerView.register(SPHomeBannerCell.self, forCellWithReuseIdentifier: "bannerCell")
bannerView.hidesPageControl = true
bannerView.snp.makeConstraints { make in
private lazy var bannerLayout: SPZoomCollectionViewLayout = {
let layout = SPZoomCollectionViewLayout()
layout.itemSize = .init(width: 234, height: Self.bannerHeight())
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 0
return layout
}()
private lazy var bannerView: SPCollectionView = {
let view = SPCollectionView(frame: .zero, collectionViewLayout: bannerLayout)
view.delegate = self
view.dataSource = self
view.showsVerticalScrollIndicator = false
view.showsHorizontalScrollIndicator = false
SPHomeZoomBannerCell.registerCell(collectionView: view)
view.snp.makeConstraints { make in
make.width.equalTo(kSPScreenWidth)
make.height.equalTo(Self.bannerHeight())
}
return bannerView
return view
}()
private lazy var playHistoryView: SPHomePlayHistoryView = {
let view = SPHomePlayHistoryView()
return view
}()
private lazy var exploreView: SPHomeExploreView = {
let view = SPHomeExploreView()
return view
}()
private lazy var trendingView: SPHomeTrendingView = {
@ -67,6 +98,12 @@ class SPHomeHeaderView: UICollectionReusableView {
return view
}()
private lazy var titleView: SPHomeDataItemView = {
let view = SPHomeDataItemView()
view.titleLabel.text = "More for you!".localized
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
_setupUI()
@ -90,50 +127,65 @@ extension SPHomeHeaderView {
}
//MARK: -------------- ZKCycleScrollViewDelegate & ZKCycleScrollViewDataSource --------------
extension SPHomeHeaderView: ZKCycleScrollViewDelegate, ZKCycleScrollViewDataSource {
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
extension SPHomeHeaderView: UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfItems(in cycleScrollView: ZKCycleScrollView) -> Int {
return moduleModel?.bannerData?.count ?? 0
}
func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, cellForItemAt index: Int) -> ZKCycleScrollViewCell {
let cell = cycleScrollView.dequeueReusableCell(withReuseIdentifier: "bannerCell", for: index) as! SPHomeBannerCell
cell.model = moduleModel?.bannerData?[index]
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = SPHomeZoomBannerCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
cell.model = self.viewModel?.moduleModel?.bannerData?[indexPath.row]
return cell
}
func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, didSelectItemAt index: Int) {
let model = moduleModel?.bannerData?[index]
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.viewModel?.moduleModel?.bannerData?.count ?? 0
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = self.viewModel?.moduleModel?.bannerData?[indexPath.row]
let vc = SPPlayerDetailViewController()
vc.shortPlayId = model?.short_play_id
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}
}
extension SPHomeHeaderView {
static func contentHeight(model: SPHomeModuleModel) -> CGFloat {
static func contentHeight(viewModel: SPHomeViewModel) -> CGFloat {
var height = bannerHeight()
let moduleModel = viewModel.moduleModel
// if (model.recommandData?.count ?? 0) > 0 {
// }
height = height + SPHomeTrendingView.contentHeight(dataArr: model.bannerData ?? []) + 25
///
if let historyList = viewModel.playHistoryArr, historyList.count > 0 {
height = height + SPHomePlayHistoryView.contentHeight(dataArr: historyList) + 25
}
height = height + SPHomeHotView.contentHeight(dataArr: model.bannerData ?? []) + 25
///
if let list = moduleModel?.nineSquare?.list, list.count > 0 {
height = height + SPHomeExploreView.contentHeight(dataArr: list) + 25
}
height = height + SPHomeShortsForYouView.contentHeight(dataArr: model.bannerData ?? []) + 25
// height = height + SPHomeTrendingView.contentHeight(dataArr: moduleModel?.bannerData ?? []) + 25
//
//
// height = height + SPHomeHotView.contentHeight(dataArr: moduleModel?.bannerData ?? []) + 25
//
// height = height + SPHomeShortsForYouView.contentHeight(dataArr: moduleModel?.bannerData ?? []) + 25
///
height = height + SPHomeDataItemView.contentHeight(dataArr: []) + 25
return height
}
static func bannerHeight() -> CGFloat {
return 183
return 336
}

View File

@ -0,0 +1,116 @@
//
// SPHomePlayHistoryCell.swift
// Thimra
//
// Created by Overseas on 2025/4/22.
//
import UIKit
class SPHomePlayHistoryCell: SPCollectionViewCell {
var model: SPShortModel? {
didSet {
coverImageView.sp_setImage(url: model?.image_url)
titleLabel.text = model?.name
// model?.episode_total
let episode_total = "\(model?.episode_total ?? 0)"
let episode = String(format: "EP.%@/%@", "\(model?.current_episode ?? "0")", episode_total)
let episodeString = NSMutableAttributedString(string: episode)
episodeString.color = .colorFF5100()
let range = NSRange(location: episode.length() - episode_total.length() - 1, length: episode_total.length() + 1)
episodeString.setColor(.colorAFAFAF(), range: range)
episodeLabel.attributedText = episodeString
}
}
private lazy var coverImageView: SPImageView = {
let imageView = SPImageView()
imageView.layer.cornerRadius = 44
imageView.layer.masksToBounds = true
return imageView
}()
///
private lazy var maskLayerView: UIView = {
let view = UIView()
view.backgroundColor = .color000000(alpha: 0.3)
return view
}()
private lazy var playImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "play_icon_04"))
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontWeight(ofSize: 12, weight: 200)
label.textColor = .colorFFFFFF()
label.numberOfLines = 2
return label
}()
private lazy var episodeLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 10)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomePlayHistoryCell {
private func _setupUI() {
contentView.addSubview(coverImageView)
coverImageView.addSubview(maskLayerView)
coverImageView.addSubview(playImageView)
contentView.addSubview(titleLabel)
contentView.addSubview(episodeLabel)
coverImageView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.centerX.equalToSuperview()
make.width.height.equalTo(88)
}
maskLayerView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
playImageView.snp.makeConstraints { make in
make.center.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.equalTo(coverImageView.snp.bottom).offset(6)
make.right.lessThanOrEqualToSuperview()
}
episodeLabel.snp.makeConstraints { make in
make.left.equalToSuperview()
make.bottom.equalToSuperview()
}
}
}

View File

@ -0,0 +1,91 @@
//
// SPHomePlayHistoryView.swift
// Thimra
//
// Created by Overseas on 2025/4/22.
//
import UIKit
class SPHomePlayHistoryView: SPHomeDataItemView {
override class func contentHeight(dataArr: [SPShortModel]) -> CGFloat {
let height = self.contentToTop + 136
return height
}
override var dataArr: [SPShortModel]? {
didSet {
self.collectionView.reloadData()
}
}
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 88, height: 136)
layout.sectionInset = .init(top: 0, left: 16, bottom: 0, right: 16)
layout.minimumInteritemSpacing = 16
layout.minimumLineSpacing = 16
return layout
}()
private lazy var collectionView: SPCollectionView = {
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
SPHomePlayHistoryCell.registerCell(collectionView: collectionView)
return collectionView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.titleLabel.text = "Continue watching".localized
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomePlayHistoryView {
private func _setupUI() {
contentView.addSubview(self.collectionView)
self.collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
extension SPHomePlayHistoryView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = SPHomePlayHistoryCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
cell.model = dataArr?[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArr?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = dataArr?[indexPath.row]
let vc = SPPlayerDetailViewController()
vc.shortPlayId = model?.short_play_id
vc.playHistoryModel = model
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}
}

View File

@ -11,7 +11,7 @@ class SPHomeSearchButton: UIControl {
override var intrinsicContentSize: CGSize {
return CGSize(width: kSPScreenWidth, height: 38)
return CGSize(width: kSPScreenWidth, height: 32)
}
private lazy var iconImageView: UIImageView = {
@ -19,9 +19,17 @@ class SPHomeSearchButton: UIControl {
return imageView
}()
private lazy var textLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
label.textColor = .colorFFFFFF(alpha: 0.52)
label.text = "Love Me Like You Do It".localized
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
layer.cornerRadius = 19
layer.cornerRadius = 16
layer.masksToBounds = true
backgroundColor = .colorFFFFFF(alpha: 0.1)
@ -38,11 +46,18 @@ class SPHomeSearchButton: UIControl {
extension SPHomeSearchButton {
private func _setupUI() {
addSubview(textLabel)
addSubview(iconImageView)
textLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(14)
make.centerY.equalToSuperview()
make.right.lessThanOrEqualTo(self.iconImageView.snp.left).offset(-5)
}
iconImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(15)
make.right.equalToSuperview().offset(-6)
}
}

View File

@ -0,0 +1,64 @@
//
// SPHomeShortCell.swift
// Thimra
//
// Created by Overseas on 2025/4/22.
//
import UIKit
class SPHomeShortCell: SPCollectionViewCell {
var model: SPShortModel? {
didSet {
coverImageView.sp_setImage(url: model?.image_url)
titleLabel.text = model?.name
}
}
private lazy var coverImageView: SPImageView = {
let imageView = SPImageView()
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 12)
label.textColor = .colorFFFFFF()
label.numberOfLines = 2
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.layer.cornerRadius = 8
contentView.layer.masksToBounds = true
contentView.backgroundColor = .colorD9D9D9(alpha: 0.14)
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeShortCell {
private func _setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(titleLabel)
coverImageView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.bottom.equalToSuperview().offset(-44)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(8)
make.right.lessThanOrEqualToSuperview().offset(-8)
make.top.equalTo(coverImageView.snp.bottom).offset(5)
}
}
}

View File

@ -0,0 +1,86 @@
//
// SPHomeZoomBannerCell.swift
// Thimra
//
// Created by Overseas on 2025/4/22.
//
import UIKit
class SPHomeZoomBannerCell: SPCollectionViewCell {
var model: SPShortModel? {
didSet {
coverImageView.sp_setImage(url: model?.image_url)
titleLabel.text = model?.name
}
}
private lazy var coverImageView: SPImageView = {
let imageView = SPImageView()
return imageView
}()
private lazy var iconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "new_icon_01"))
return imageView
}()
private lazy var bottomView: UIView = {
let view = UIView()
view.addEffectView(style: .light)
return view
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 16)
label.textColor = .colorFFFFFF()
label.numberOfLines = 2
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.layer.cornerRadius = 8
contentView.layer.masksToBounds = true
_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SPHomeZoomBannerCell {
private func _setupUI() {
contentView.addSubview(coverImageView)
coverImageView.addSubview(iconImageView)
coverImageView.addSubview(bottomView)
bottomView.addSubview(titleLabel)
coverImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
iconImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.top.equalToSuperview().offset(12)
}
bottomView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(72)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.right.lessThanOrEqualToSuperview().offset(-15)
make.centerY.equalToSuperview()
}
}
}

View File

@ -0,0 +1,17 @@
//
// SPHomeViewModel.swift
// Thimra
//
// Created by Overseas on 2025/4/22.
//
import UIKit
class SPHomeViewModel: NSObject {
var moduleModel: SPHomeModuleModel?
///
var playHistoryArr: [SPShortModel]?
}

View File

@ -62,6 +62,11 @@ class SPEpisodeView: HWPanModalContentView {
var isDragging = false
//MARK: UI
private lazy var bgView: UIView = {
let view = UIImageView(image: UIImage(named: "episode_bg_image_01"))
return view
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let itemWidth = floor((kSPScreenWidth - 10 * 4 - 30) / 5)
@ -91,23 +96,25 @@ class SPEpisodeView: HWPanModalContentView {
private lazy var coverImageView: SPImageView = {
let imageView = SPImageView()
imageView.layer.cornerRadius = 6
imageView.layer.cornerRadius = 4
imageView.layer.masksToBounds = true
imageView.layer.borderColor = UIColor.colorFFFFFF(alpha: 0.26).cgColor
imageView.layer.borderWidth = 1
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 15)
label.font = .fontMedium(ofSize: 14)
label.textColor = .colorFFFFFF()
return label
}()
private lazy var desLabel: UILabel = {
let label = UILabel()
label.font = .fontLight(ofSize: 12)
label.textColor = .color9D9D9D()
label.numberOfLines = 0
label.font = .fontRegular(ofSize: 10)
label.textColor = .colorA8A5AA()
label.numberOfLines = 5
return label
}()
@ -165,10 +172,10 @@ class SPEpisodeView: HWPanModalContentView {
return config
}
override func present(in view: UIView?) {
super.present(in: view)
self.hw_contentView.addEffectView(style: .dark)
}
// override func present(in view: UIView?) {
// super.present(in: view)
// self.hw_contentView.addEffectView(style: .dark)
// }
@ -177,6 +184,7 @@ class SPEpisodeView: HWPanModalContentView {
extension SPEpisodeView {
private func _setupUI() {
addSubview(bgView)
addSubview(indicatorView)
addSubview(coverImageView)
addSubview(titleLabel)
@ -185,6 +193,11 @@ extension SPEpisodeView {
addSubview(lineView)
addSubview(collectionView)
bgView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
self.indicatorView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(15)
@ -195,18 +208,18 @@ extension SPEpisodeView {
self.coverImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview().offset(39)
make.width.equalTo(55)
make.height.equalTo(74)
make.width.equalTo(70)
make.height.equalTo(104)
}
self.titleLabel.snp.makeConstraints { make in
make.left.equalTo(self.coverImageView.snp.right).offset(12)
make.right.lessThanOrEqualToSuperview().offset(-15)
make.centerY.equalTo(self.coverImageView)
make.right.lessThanOrEqualToSuperview().offset(-16)
make.top.equalTo(self.coverImageView)
}
self.desLabel.snp.makeConstraints { make in
make.left.equalTo(self.coverImageView)
make.left.equalTo(self.titleLabel)
make.right.lessThanOrEqualToSuperview().offset(-15)
make.top.equalTo(self.coverImageView.snp.bottom).offset(8)
}

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame 155@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame 155@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "💗@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "💗@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame 127@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame 127@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

View File

@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "搜索图标@2x.png",
"filename" : "Frame@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "搜索图标@3x.png",
"filename" : "Frame@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "背景@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "背景@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

View File

@ -33,6 +33,11 @@
"Cancel" = "Cancel";
"Select All" = "Select All";
"Delet (%@)" = "Delet (%@)";
"Love Me Like You Do It" = "Love Me Like You Do It";
"Explore For You" = "Explore For You";
"Continue watching" = "Continue watching";
"More for you!" = "More for you!";
"More" = "More";
///视频详情标题
"kPlayerDetailTitleString" = "Episode %@ / %@";