首页开发完成
@ -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"))
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -8,31 +8,27 @@
|
||||
import UIKit
|
||||
|
||||
/*
|
||||
https://api-moviatv.moviatv.com/93f03506/
|
||||
https://admin-thimratv.guyantv.com/login
|
||||
|
||||
https://api-mireotv.mireotv.com/4da6fd4c/
|
||||
https://api-thimratv.thimratv.com/0a2c5b02/
|
||||
|
||||
https://api-vibeoshort.vibeoshort.com/bf86d973/
|
||||
|
||||
https://api-viontv.viontv.com/b7afef99/
|
||||
|
||||
https://api-zyreotv.zyreotv.com/7834f11d/
|
||||
|
||||
https://api-thimratv.thimratv.com
|
||||
新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
|
||||
|
||||
|
||||
|
107
Thimra/Base/View/SPZoomCollectionViewLayout.swift
Normal 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
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -17,7 +17,7 @@ class SPHomeChildController: SPViewController {
|
||||
|
||||
}
|
||||
|
||||
override func setBgImageView() { }
|
||||
// override func setBgImageView() { }
|
||||
|
||||
|
||||
|
||||
|
@ -9,13 +9,36 @@ 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?()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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?
|
||||
}
|
||||
|
@ -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)
|
||||
|
65
Thimra/Class/Home/View/SPHomeExploreCell.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
109
Thimra/Class/Home/View/SPHomeExploreView.swift
Normal 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)
|
||||
}
|
||||
}
|
@ -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
|
||||
if let list = moduleModel?.nineSquare?.list, list.count > 0 {
|
||||
stackView.addArrangedSubview(exploreView)
|
||||
exploreView.titleLabel.text = moduleModel?.nineSquare?.title
|
||||
exploreView.dataArr = list
|
||||
}
|
||||
|
||||
stackView.addArrangedSubview(shortsForYouView)
|
||||
shortsForYouView.dataArr = moduleModel?.bannerData
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
|
116
Thimra/Class/Home/View/SPHomePlayHistoryCell.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
91
Thimra/Class/Home/View/SPHomePlayHistoryView.swift
Normal 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)
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
64
Thimra/Class/Home/View/SPHomeShortCell.swift
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
86
Thimra/Class/Home/View/SPHomeZoomBannerCell.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
17
Thimra/Class/Home/ViewModel/SPHomeViewModel.swift
Normal 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]?
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
22
Thimra/Source/Assets.xcassets/icon/logo_icon_01.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Thimra/Source/Assets.xcassets/icon/logo_icon_01.imageset/Frame 155@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Thimra/Source/Assets.xcassets/icon/logo_icon_01.imageset/Frame 155@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.6 KiB |
22
Thimra/Source/Assets.xcassets/icon/mark_icon_01.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Thimra/Source/Assets.xcassets/icon/mark_icon_01.imageset/💗@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
Thimra/Source/Assets.xcassets/icon/mark_icon_01.imageset/💗@3x.png
vendored
Normal file
After Width: | Height: | Size: 5.2 KiB |
22
Thimra/Source/Assets.xcassets/icon/new_icon_01.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Thimra/Source/Assets.xcassets/icon/new_icon_01.imageset/Frame 127@2x.png
vendored
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
Thimra/Source/Assets.xcassets/icon/new_icon_01.imageset/Frame 127@3x.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
22
Thimra/Source/Assets.xcassets/icon/play_icon_04.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Thimra/Source/Assets.xcassets/icon/play_icon_04.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 502 B |
BIN
Thimra/Source/Assets.xcassets/icon/play_icon_04.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 686 B |
@ -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"
|
||||
}
|
||||
|
BIN
Thimra/Source/Assets.xcassets/icon/search_icon_01.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 537 B |
BIN
Thimra/Source/Assets.xcassets/icon/search_icon_01.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 775 B |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.8 KiB |
22
Thimra/Source/Assets.xcassets/image/episode_bg_image_01.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Thimra/Source/Assets.xcassets/image/episode_bg_image_01.imageset/背景@2x.png
vendored
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
Thimra/Source/Assets.xcassets/image/episode_bg_image_01.imageset/背景@3x.png
vendored
Normal file
After Width: | Height: | Size: 554 KiB |
@ -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 %@ / %@";
|
||||
|