收藏列表,播放记录列表
@ -16,9 +16,11 @@ class SPTabBarController: UITabBarController {
|
||||
|
||||
let nav2 = createNavigationController(viewController: SPExploreViewController(), title: "For You".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected"))
|
||||
|
||||
let nav4 = createNavigationController(viewController: SPMyListViewController(), title: "My list".localized, image: UIImage(named: "tabbar_icon_04"), selectedImage: UIImage(named: "tabbar_icon_04_selected"))
|
||||
|
||||
let nav5 = createNavigationController(viewController: SPMineViewController(), title: "Profile".localized, image: UIImage(named: "tabbar_icon_05"), selectedImage: UIImage(named: "tabbar_icon_05_selected"))
|
||||
|
||||
self.viewControllers = [nav1, nav2, nav5]
|
||||
self.viewControllers = [nav1, nav2, nav4, nav5]
|
||||
|
||||
}
|
||||
|
||||
|
@ -91,6 +91,10 @@ class SPViewController: UIViewController, JYPageChildContollerProtocol {
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleHeaderRefresh(_ completer: (() -> Void)?) {}
|
||||
|
||||
func handleFooterRefresh(_ completer: (() -> Void)?) {}
|
||||
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
|
@ -76,5 +76,13 @@ extension UIColor {
|
||||
static func colorD568D2(alpha: CGFloat = 1) -> UIColor {
|
||||
return color(hex: 0xD568D2, alpha: alpha)
|
||||
}
|
||||
|
||||
static func color8A899F(alpha: CGFloat = 1) -> UIColor {
|
||||
return color(hex: 0x8A899F, alpha: alpha)
|
||||
}
|
||||
|
||||
static func color888888(alpha: CGFloat = 1) -> UIColor {
|
||||
return color(hex: 0x888888, alpha: alpha)
|
||||
}
|
||||
}
|
||||
|
||||
|
64
ShortPlay/Base/Extension/UIScrollView+SPRefresh.swift
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// UIScrollView+SPRefresh.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/19.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIScrollView {
|
||||
func sp_addRefreshHeader(insetTop: CGFloat = 0, block: (() -> Void)?) {
|
||||
|
||||
|
||||
self.mj_header = MJRefreshNormalHeader(refreshingBlock: {
|
||||
block?()
|
||||
})
|
||||
self.mj_header?.ignoredScrollViewContentInsetTop = insetTop
|
||||
}
|
||||
|
||||
func sp_addRefreshFooter(insetBottom: CGFloat = 0, block: (() -> Void)?) {
|
||||
let footer = MJRefreshAutoNormalFooter(refreshingBlock: {
|
||||
block?()
|
||||
})
|
||||
footer.ignoredScrollViewContentInsetBottom = insetBottom
|
||||
// footer.stateLabel?.font = .text_sm
|
||||
// footer.stateLabel?.textColor = .system_text_secondary_300
|
||||
|
||||
self.mj_footer = footer
|
||||
|
||||
|
||||
}
|
||||
|
||||
// func sp_setRefreshFooterTitle(title: String = NSLocalizedString("已经到底了~", comment: ""), state: MJRefreshState) {
|
||||
// (self.mj_footer as? MJRefreshAutoStateFooter)?.setTitle(title, for: state)
|
||||
// }
|
||||
|
||||
func sp_addRefreshBackFooter(insetBottom: CGFloat = 0, block: (() -> Void)?) {
|
||||
self.mj_footer = MJRefreshBackNormalFooter(refreshingBlock: {
|
||||
block?()
|
||||
})
|
||||
|
||||
self.mj_footer?.ignoredScrollViewContentInsetBottom = insetBottom
|
||||
}
|
||||
|
||||
func sp_endHeaderRefreshing() {
|
||||
self.mj_header?.endRefreshing()
|
||||
}
|
||||
|
||||
func sp_endFooterRefreshing() {
|
||||
if self.mj_footer?.state == .noMoreData { return }
|
||||
self.mj_footer?.endRefreshing()
|
||||
}
|
||||
|
||||
///重置没有更多
|
||||
func sp_resetNoMoreData() {
|
||||
self.mj_footer?.resetNoMoreData()
|
||||
}
|
||||
|
||||
func sp_endRefreshingWithNoMoreData() {
|
||||
// self.mj_footer?.state = .noMoreData
|
||||
self.mj_footer?.endRefreshingWithNoMoreData()
|
||||
}
|
||||
|
||||
}
|
@ -46,5 +46,19 @@ class SPHomeAPI: NSObject {
|
||||
|
||||
}
|
||||
|
||||
///搜索
|
||||
static func requestSearch(text: String, completer: ((_ list: [SPShortModel]?) -> Void)?) {
|
||||
var param = SPNetworkParameters(path: "/search")
|
||||
param.method = .get
|
||||
param.parameters = [
|
||||
"search" : text
|
||||
]
|
||||
|
||||
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPListModel<SPShortModel>>) in
|
||||
completer?(response.data?.list)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
23
ShortPlay/Base/Networking/API/SPUserAPI.swift
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// SPUserAPI.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPUserAPI: NSObject {
|
||||
|
||||
|
||||
static func requestUserInfo(completer: ((_ userInfo: SPUserInfo?) -> Void)?) {
|
||||
|
||||
var param = SPNetworkParameters(path: "/customer/info")
|
||||
param.method = .get
|
||||
|
||||
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPUserInfo>) in
|
||||
completer?(response.data)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -69,6 +69,20 @@ class SPVideoAPI: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
///收藏列表
|
||||
static func requestCollectList(page: Int, completer: ((_ listModel: SPListModel<SPShortModel>?) -> Void)?) {
|
||||
var param = SPNetworkParameters(path: "/myCollections")
|
||||
param.method = .get
|
||||
param.parameters = [
|
||||
"current_page" : page,
|
||||
"page_size" : 20
|
||||
]
|
||||
|
||||
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPListModel<SPShortModel>>) in
|
||||
completer?(response.data)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPVideoAPI {
|
||||
|
@ -51,7 +51,7 @@ extension SPApi: TargetType {
|
||||
var path: String {
|
||||
switch self {
|
||||
case .request(let parameters):
|
||||
return parameters.path
|
||||
return SPURLPathPrefix + parameters.path
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
@ -72,44 +72,36 @@ class SPNetwork: NSObject {
|
||||
let code = response.statusCode
|
||||
if code == 401 || code == 402 || code == 403 {
|
||||
|
||||
///重新获取token
|
||||
self.requestToken(completer: nil)
|
||||
|
||||
///将请求失败数据重新请求
|
||||
if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" {
|
||||
|
||||
let requestOperation = BlockOperation {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
_request(parameters: parameters) { (response: SPNetworkResponse<T>) in
|
||||
semaphore.signal()
|
||||
completion?(response)
|
||||
}
|
||||
semaphore.wait()
|
||||
if parameters.path == "/customer/register" {
|
||||
var res = SPNetworkResponse<T>()
|
||||
res.code = -1
|
||||
if parameters.isToast {
|
||||
SPToast.show(text: "Error".localized)
|
||||
}
|
||||
///设置依赖关系
|
||||
requestOperation.addDependency(tokenOperation)
|
||||
completion?(res)
|
||||
} else {
|
||||
///重新获取token
|
||||
self.requestToken(completer: nil)
|
||||
|
||||
operationQueue.addOperation(requestOperation)
|
||||
///将请求失败数据重新请求
|
||||
if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" {
|
||||
|
||||
let requestOperation = BlockOperation {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
_request(parameters: parameters) { (response: SPNetworkResponse<T>) in
|
||||
semaphore.signal()
|
||||
completion?(response)
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
///设置依赖关系
|
||||
requestOperation.addDependency(tokenOperation)
|
||||
|
||||
operationQueue.addOperation(requestOperation)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// if !SPLoginManager.manager.isRefreshingToken {
|
||||
// SPLoginManager.manager.requestVisitorLogin {
|
||||
// if let _ = SPLoginManager.manager.token {
|
||||
// self.request(parameters: parameters, completion: completion)
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// if parameters.path != "/customer/register" {
|
||||
//// while SPLoginManager.manager.isRefreshingToken {
|
||||
//// RunLoop.current.run(mode: .default, before: Date.distantFuture)
|
||||
//// }
|
||||
//// if let _ = SPLoginManager.manager.token {
|
||||
//// self.request(parameters: parameters, completion: completion)
|
||||
//// }
|
||||
// }
|
||||
// }
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -21,9 +21,14 @@ import UIKit
|
||||
|
||||
#if DEBUG
|
||||
let SPBaseURL = "https://test1-api.guyantv.com"
|
||||
let SPURLPathPrefix = ""
|
||||
//let SPBaseURL = "https://api-mireotv.mireotv.com"
|
||||
//let SPURLPathPrefix = "/4da6fd4c"
|
||||
|
||||
let SPWebBaseURL = "https://www.guyantv.com"
|
||||
#else
|
||||
let SPBaseURL = "https://test1-api.guyantv.com"
|
||||
let SPURLPathPrefix = "/4da6fd4c"
|
||||
let SPWebBaseURL = "https://www.guyantv.com"
|
||||
#endif
|
||||
|
||||
|
@ -19,6 +19,29 @@ class SPSearchViewController: SPViewController {
|
||||
private lazy var searchInputView: SPSearchInputView = {
|
||||
let view = SPSearchInputView()
|
||||
view.textField.delegate = self
|
||||
view.textDidChange = { [weak self] text in
|
||||
self?.textDidChange(text: text)
|
||||
}
|
||||
return view
|
||||
}()
|
||||
|
||||
///搜索首页
|
||||
private lazy var homeView: SPSearchHomeView = {
|
||||
let view = SPSearchHomeView()
|
||||
return view
|
||||
}()
|
||||
|
||||
///联想页面
|
||||
private lazy var associativeView: SPSearchAssociativeView = {
|
||||
let view = SPSearchAssociativeView()
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
///搜索结果
|
||||
private lazy var resultView: SPSearchResultView = {
|
||||
let view = SPSearchResultView()
|
||||
view.isHidden = true
|
||||
return view
|
||||
}()
|
||||
|
||||
@ -41,6 +64,9 @@ extension SPSearchViewController {
|
||||
private func _setupUI() {
|
||||
view.addSubview(backButton)
|
||||
view.addSubview(searchInputView)
|
||||
view.addSubview(homeView)
|
||||
view.addSubview(resultView)
|
||||
view.addSubview(associativeView)
|
||||
|
||||
backButton.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(5)
|
||||
@ -54,6 +80,19 @@ extension SPSearchViewController {
|
||||
make.left.equalTo(backButton.snp.right).offset(5)
|
||||
}
|
||||
|
||||
homeView.snp.makeConstraints { make in
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
make.top.equalTo(searchInputView.snp.bottom).offset(10)
|
||||
}
|
||||
|
||||
resultView.snp.makeConstraints { make in
|
||||
make.edges.equalTo(homeView)
|
||||
}
|
||||
|
||||
associativeView.snp.makeConstraints { make in
|
||||
make.edges.equalTo(homeView)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -69,4 +108,31 @@ extension SPSearchViewController: UITextFieldDelegate {
|
||||
spLog(message: "结束编辑")
|
||||
}
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
spLog(message: "点击搜索")
|
||||
if let text = textField.text, text.count > 0 {
|
||||
// self.requestSearch(text: text)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
///文本发生变化
|
||||
func textDidChange(text: String) {
|
||||
if text.count > 0 {
|
||||
self.associativeView.isHidden = false
|
||||
self.homeView.isHidden = true
|
||||
} else {
|
||||
self.associativeView.isHidden = true
|
||||
self.homeView.isHidden = false
|
||||
}
|
||||
self.associativeView.search(text: text)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPSearchViewController {
|
||||
|
||||
|
||||
}
|
||||
|
75
ShortPlay/Class/Home/View/SPSearchAssociativeCell.swift
Normal file
@ -0,0 +1,75 @@
|
||||
//
|
||||
// SPSearchAssociativeCell.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPSearchAssociativeCell: SPTableViewCell {
|
||||
|
||||
|
||||
var searchText: String?
|
||||
|
||||
var model: SPShortModel? {
|
||||
didSet {
|
||||
|
||||
if let string = model?.titleAttributedString {
|
||||
titleLabel.attributedText = string
|
||||
} else {
|
||||
let string = NSMutableAttributedString(string: model?.name ?? "")
|
||||
string.color = .color8A899F()
|
||||
|
||||
if let range = model?.name?.ocString().range(of: searchText ?? "") {
|
||||
string.setColor(.colorFFFFFF(), range: range)
|
||||
}
|
||||
|
||||
titleLabel.attributedText = string
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var iconImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "play_icon_03"))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontRegular(ofSize: 14)
|
||||
return label
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
_setupUI()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPSearchAssociativeCell {
|
||||
|
||||
private func _setupUI() {
|
||||
contentView.addSubview(iconImageView)
|
||||
contentView.addSubview(titleLabel)
|
||||
|
||||
iconImageView.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalToSuperview().offset(15)
|
||||
}
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalToSuperview().offset(40)
|
||||
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
96
ShortPlay/Class/Home/View/SPSearchAssociativeView.swift
Normal file
@ -0,0 +1,96 @@
|
||||
//
|
||||
// SPSearchAssociativeView.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPSearchAssociativeView: UIView {
|
||||
|
||||
|
||||
private(set) lazy var searchText: String = ""
|
||||
|
||||
private lazy var dataArr: [SPShortModel] = []
|
||||
|
||||
private lazy var tableView: SPTableView = {
|
||||
let tableView = SPTableView(frame: .zero, style: .plain)
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
tableView.rowHeight = 50
|
||||
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: kSPTabbarSafeBottomMargin, right: 0)
|
||||
SPSearchAssociativeCell.registerCell(tableView: tableView)
|
||||
return tableView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
_setupUI()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func search(text: String) {
|
||||
self.searchText = text
|
||||
|
||||
self.requestSearch(text: text)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension SPSearchAssociativeView {
|
||||
|
||||
private func _setupUI() {
|
||||
addSubview(tableView)
|
||||
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- UITableViewDelegate & UITableViewDataSource --------------
|
||||
extension SPSearchAssociativeView: UITableViewDelegate, UITableViewDataSource {
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = SPSearchAssociativeCell.dequeueReusableCell(tableView: tableView, indexPath: indexPath)
|
||||
cell.searchText = self.searchText
|
||||
cell.model = dataArr[indexPath.row]
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return dataArr.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let model = self.dataArr[indexPath.row]
|
||||
|
||||
let vc = SPPlayerDetailViewController()
|
||||
vc.shortPlayId = model.short_play_id
|
||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension SPSearchAssociativeView {
|
||||
|
||||
private func requestSearch(text: String) {
|
||||
|
||||
SPHomeAPI.requestSearch(text: text) { [weak self] list in
|
||||
guard let self = self else { return }
|
||||
if self.searchText != text { return; }
|
||||
|
||||
if let list = list {
|
||||
self.dataArr = list
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -19,6 +19,8 @@ class SPSearchInputView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
var textDidChange: ((_ text: String) -> Void)?
|
||||
|
||||
//MARK: UI属性
|
||||
private lazy var iconImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "search_icon_02"))
|
||||
@ -30,6 +32,10 @@ class SPSearchInputView: UIView {
|
||||
private(set) lazy var textField: SPTextField = {
|
||||
let textField = SPTextField()
|
||||
textField.font = .fontRegular(ofSize: 12)
|
||||
textField.returnKeyType = .search
|
||||
textField.textDidChange = { [weak self] text in
|
||||
self?.textDidChange?(text)
|
||||
}
|
||||
return textField
|
||||
}()
|
||||
|
||||
|
20
ShortPlay/Class/Home/View/SPSearchResultCell.swift
Normal file
@ -0,0 +1,20 @@
|
||||
//
|
||||
// SPSearchResultCell.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPSearchResultCell: SPTableViewCell {
|
||||
|
||||
/*
|
||||
// Only override draw() if you perform custom drawing.
|
||||
// An empty implementation adversely affects performance during animation.
|
||||
override func draw(_ rect: CGRect) {
|
||||
// Drawing code
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
71
ShortPlay/Class/Home/View/SPSearchResultView.swift
Normal file
@ -0,0 +1,71 @@
|
||||
//
|
||||
// SPSearchResultView.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPSearchResultView: UIView {
|
||||
|
||||
|
||||
private lazy var tableView: SPTableView = {
|
||||
let tableView = SPTableView(frame: .zero, style: .plain)
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
SPSearchResultCell.registerCell(tableView: tableView)
|
||||
return tableView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
// _setupUI()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPSearchResultView {
|
||||
|
||||
private func _setupUI() {
|
||||
addSubview(tableView)
|
||||
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- UITableViewDelegate & UITableViewDataSource --------------
|
||||
extension SPSearchResultView: UITableViewDelegate, UITableViewDataSource {
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return 10
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = SPSearchResultCell.dequeueReusableCell(tableView: tableView, indexPath: indexPath)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPSearchResultView {
|
||||
|
||||
private func requestSearch(text: String) {
|
||||
|
||||
// SPHomeAPI.requestSearch(text: text)
|
||||
|
||||
}
|
||||
}
|
14
ShortPlay/Class/Home/ViewModel/SPSearchViewModel.swift
Normal file
@ -0,0 +1,14 @@
|
||||
//
|
||||
// SPSearchViewModel.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPSearchViewModel: NSObject {
|
||||
|
||||
|
||||
|
||||
}
|
@ -33,6 +33,8 @@ class SPMineViewController: SPViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
requestUserInfo()
|
||||
|
||||
_setupUI()
|
||||
}
|
||||
|
||||
@ -90,6 +92,12 @@ extension SPMineViewController: UITableViewDelegate, UITableViewDataSource {
|
||||
|
||||
extension SPMineViewController {
|
||||
|
||||
// func
|
||||
private func requestUserInfo() {
|
||||
|
||||
SPUserAPI.requestUserInfo { userInfo in
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,121 @@
|
||||
//
|
||||
// SPCollectListViewController.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPCollectListViewController: SPViewController {
|
||||
|
||||
|
||||
private lazy var dataArr: [SPShortModel] = []
|
||||
private var page: Int?
|
||||
|
||||
//MARK: UI属性
|
||||
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||
let itemWidth = floor((kSPScreenWidth - 30 - 9 * 2) / 3)
|
||||
let itemHeight = 146 / 109 * itemWidth + 36
|
||||
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.itemSize = .init(width: itemWidth, height: itemHeight)
|
||||
layout.minimumLineSpacing = 10
|
||||
layout.minimumInteritemSpacing = 9
|
||||
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
|
||||
|
||||
return layout
|
||||
}()
|
||||
|
||||
private lazy var collectionView: SPCollectionView = {
|
||||
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||
collectionView.delegate = self
|
||||
collectionView.dataSource = self
|
||||
collectionView.contentInset = .init(top: 10, left: 0, bottom: 0, right: 0)
|
||||
collectionView.sp_addRefreshHeader(insetTop: collectionView.contentInset.top) { [weak self] in
|
||||
self?.handleHeaderRefresh(nil)
|
||||
}
|
||||
collectionView.sp_addRefreshFooter { [weak self] in
|
||||
self?.handleFooterRefresh(nil)
|
||||
}
|
||||
SPCollectListCell.registerCell(collectionView: collectionView)
|
||||
return collectionView
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
||||
requestDataList(page: 1, completer: nil)
|
||||
|
||||
_setupUI()
|
||||
}
|
||||
|
||||
override func setBgImageView() { }
|
||||
|
||||
|
||||
override func handleHeaderRefresh() {
|
||||
requestDataList(page: 1) { [weak self] in
|
||||
self?.collectionView.sp_endHeaderRefreshing()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPCollectListViewController {
|
||||
|
||||
private func _setupUI() {
|
||||
view.addSubview(collectionView)
|
||||
|
||||
collectionView.snp.makeConstraints { make in
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
make.top.equalToSuperview().offset(10)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
|
||||
extension SPCollectListViewController: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell = SPCollectListCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
|
||||
cell.model = dataArr[indexPath.row]
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return self.dataArr.count
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPCollectListViewController {
|
||||
|
||||
private func requestDataList(page: Int, completer: (() -> Void)?) {
|
||||
|
||||
SPVideoAPI.requestCollectList(page: page) { [weak self] listModel in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let listModel = listModel, let list = listModel.list {
|
||||
if page == 1 {
|
||||
self.dataArr.removeAll()
|
||||
}
|
||||
self.dataArr += list
|
||||
|
||||
self.page = page
|
||||
|
||||
self.collectionView.reloadData()
|
||||
|
||||
}
|
||||
completer?()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,91 @@
|
||||
//
|
||||
// SPMyListViewController.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPMyListViewController: SPViewController {
|
||||
|
||||
|
||||
private lazy var titles = ["Follow List".localized, "Play List".localized]
|
||||
|
||||
private lazy var viewControllers: [SPViewController] = {
|
||||
let vc1 = SPCollectListViewController()
|
||||
let vc2 = SPPlayHistoryViewController()
|
||||
return [vc1, vc2]
|
||||
}()
|
||||
|
||||
private lazy var pageView: JYPageController = {
|
||||
let pageView = JYPageController()
|
||||
pageView.delegate = self
|
||||
pageView.dataSource = self
|
||||
pageView.config.indicatorWidth = 20
|
||||
pageView.config.indicatorHeight = 4
|
||||
pageView.config.indicatorCornerRadius = 2
|
||||
pageView.config.indicatorColor = .colorFFFFFF(alpha: 0.9)
|
||||
pageView.config.selectedTitleColor = .colorFFFFFF(alpha: 0.9)
|
||||
pageView.config.selectedTitleFont = 16
|
||||
pageView.config.selectedTitleFontWeight = .medium
|
||||
pageView.config.normalTitleColor = .color888888()
|
||||
pageView.config.normalTitleFont = 15
|
||||
pageView.config.normalTitleFontWeight = .regular
|
||||
pageView.config.leftPadding = 15
|
||||
pageView.config.itemsMargin = 40
|
||||
return pageView
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
_setupUI()
|
||||
}
|
||||
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
self.navigationController?.setNavigationBarHidden(true, animated: true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPMyListViewController {
|
||||
private func _setupUI() {
|
||||
addChild(pageView)
|
||||
view.addSubview(pageView.view)
|
||||
|
||||
pageView.view.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//MARK: -------------- JYPageControllerDelegate & JYPageControllerDataSource --------------
|
||||
extension SPMyListViewController: JYPageControllerDelegate, JYPageControllerDataSource {
|
||||
func pageController(_ pageController: JYPageController, frameForSegmentedView segmentedView: JYSegmentedView) -> CGRect {
|
||||
return .init(x: 0, y: kSPStatusbarHeight + 10, width: kSPScreenWidth, height: 35)
|
||||
}
|
||||
|
||||
func pageController(_ pageController: JYPageController, frameForContainerView container: UIScrollView) -> CGRect {
|
||||
let y = kSPStatusbarHeight + 10 + 35
|
||||
return .init(x: 0, y: kSPStatusbarHeight + 10 + 35, width: kSPScreenWidth, height: kSPScreenHeight - y - kSPTabBarHeight)
|
||||
}
|
||||
|
||||
func pageController(_ pageController: JYPageController, titleAt index: Int) -> String {
|
||||
return titles[index]
|
||||
}
|
||||
|
||||
func numberOfChildControllers() -> Int {
|
||||
return titles.count
|
||||
}
|
||||
|
||||
func childController(atIndex index: Int) -> any JYPageChildContollerProtocol {
|
||||
return self.viewControllers[index]
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
//
|
||||
// SPPlayHistoryViewController.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/19.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPPlayHistoryViewController: SPViewController {
|
||||
|
||||
private lazy var dataArr: [SPShortModel] = []
|
||||
private var page: Int?
|
||||
|
||||
//MARK: UI属性
|
||||
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||
let itemWidth = floor((kSPScreenWidth - 30 - 9 * 2) / 3)
|
||||
let itemHeight = 146 / 109 * itemWidth + 36
|
||||
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.itemSize = .init(width: itemWidth, height: itemHeight)
|
||||
layout.minimumLineSpacing = 10
|
||||
layout.minimumInteritemSpacing = 9
|
||||
layout.sectionInset = .init(top: 10, left: 15, bottom: 0, right: 15)
|
||||
|
||||
return layout
|
||||
}()
|
||||
|
||||
private lazy var collectionView: SPCollectionView = {
|
||||
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||
collectionView.delegate = self
|
||||
collectionView.dataSource = self
|
||||
SPCollectListCell.registerCell(collectionView: collectionView)
|
||||
return collectionView
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
||||
requestDataList(page: 1, completer: nil)
|
||||
|
||||
_setupUI()
|
||||
}
|
||||
|
||||
override func setBgImageView() { }
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPPlayHistoryViewController {
|
||||
|
||||
private func _setupUI() {
|
||||
view.addSubview(collectionView)
|
||||
|
||||
collectionView.snp.makeConstraints { make in
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
make.top.equalToSuperview().offset(10)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
|
||||
extension SPPlayHistoryViewController: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell = SPCollectListCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
|
||||
cell.model = dataArr[indexPath.row]
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return self.dataArr.count
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPPlayHistoryViewController {
|
||||
|
||||
private func requestDataList(page: Int, completer: (() -> Void)?) {
|
||||
|
||||
SPVideoAPI.requestCollectList(page: page) { [weak self] listModel in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let listModel = listModel, let list = listModel.list {
|
||||
if page == 1 {
|
||||
self.dataArr.removeAll()
|
||||
}
|
||||
self.dataArr += list
|
||||
|
||||
self.page = page
|
||||
|
||||
self.collectionView.reloadData()
|
||||
|
||||
}
|
||||
completer?()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
66
ShortPlay/Class/MyList/View/SPCollectListCell.swift
Normal file
@ -0,0 +1,66 @@
|
||||
//
|
||||
// SPCollectListCell.swift
|
||||
// ShortPlay
|
||||
//
|
||||
// Created by 曾觉新 on 2025/4/18.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPCollectListCell: SPCollectionViewCell {
|
||||
|
||||
var model: SPShortModel? {
|
||||
didSet {
|
||||
coverImageView.sp_setImage(url: model?.image_url)
|
||||
titleLabel.text = model?.name
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var coverImageView: SPImageView = {
|
||||
let imageView = SPImageView()
|
||||
imageView.layer.cornerRadius = 7
|
||||
imageView.layer.masksToBounds = true
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontRegular(ofSize: 13)
|
||||
label.textColor = .colorFFFFFF(alpha: 0.9)
|
||||
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 SPCollectListCell {
|
||||
|
||||
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.top.equalTo(coverImageView.snp.bottom).offset(5)
|
||||
make.right.lessThanOrEqualToSuperview()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -28,6 +28,9 @@ class SPShortModel: SPModel, SmartCodable {
|
||||
var video_info: SPVideoInfoModel?
|
||||
var watch_total: Int?
|
||||
|
||||
@IgnoredKey
|
||||
var titleAttributedString: NSAttributedString?
|
||||
|
||||
|
||||
static func mappingForKey() -> [SmartKeyTransformer]? {
|
||||
return [
|
||||
|
@ -11,6 +11,29 @@ import SmartCodable
|
||||
class SPUserInfo: SPModel, SmartCodable, NSSecureCoding {
|
||||
|
||||
|
||||
var id: String?
|
||||
var customer_id: String?
|
||||
var is_guide_vip: String?
|
||||
var is_tourist: String?
|
||||
var family_name: String?
|
||||
var giving_name: String?
|
||||
var vip_end_time: String?
|
||||
var third_access_id: String?
|
||||
var is_vip: Bool?
|
||||
var coin_left_total: Int?
|
||||
var vip_type: String?
|
||||
var email: String?
|
||||
var third_access_platform: String?
|
||||
var ip_address: String?
|
||||
var country_code: String?
|
||||
var user_level: String?
|
||||
var send_coin_left_total: String?
|
||||
var avator: String?
|
||||
var sign_in_status: String?
|
||||
var registered_days: String?
|
||||
var ln: String?
|
||||
var country: String?
|
||||
|
||||
|
||||
required init() { }
|
||||
|
||||
|
@ -5,12 +5,12 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Frame@2x.png",
|
||||
"filename" : "My list@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Frame@3x.png",
|
||||
"filename" : "My list@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 984 B |
Before Width: | Height: | Size: 2.0 KiB |
BIN
ShortPlay/Source/Assets.xcassets/TabBar/tabbar_icon_04.imageset/My list@2x.png
vendored
Normal file
After Width: | Height: | Size: 872 B |
BIN
ShortPlay/Source/Assets.xcassets/TabBar/tabbar_icon_04.imageset/My list@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
@ -5,10 +5,12 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "My list@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "My list@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
|
BIN
ShortPlay/Source/Assets.xcassets/TabBar/tabbar_icon_04_selected.imageset/My list@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
ShortPlay/Source/Assets.xcassets/TabBar/tabbar_icon_04_selected.imageset/My list@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.2 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/play_icon_03.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/play_icon_03.imageset/play@2x.png
vendored
Normal file
After Width: | Height: | Size: 895 B |
BIN
ShortPlay/Source/Assets.xcassets/icon/play_icon_03.imageset/play@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
@ -12,3 +12,4 @@
|
||||
#import "NSUserDefaults+JXAdd.h"
|
||||
#import <KTVHTTPCache.h>
|
||||
#import <HWPanModal/HWPanModal.h>
|
||||
#import <MJRefresh/MJRefresh.h>
|
||||
|
@ -27,6 +27,9 @@
|
||||
"User Agreement" = "User Agreement";
|
||||
"Help Center" = "Help Center";
|
||||
"About Us" = "About Us";
|
||||
"My list" = "My list";
|
||||
"Follow List" = "Follow List";
|
||||
"Play List" = "Play List";
|
||||
|
||||
///视频详情标题
|
||||
"kPlayerDetailTitleString" = "Episode %@ / %@";
|
||||
|