我的播放列表完成

This commit is contained in:
zjx 2025-05-26 14:35:45 +08:00
parent 5851400e12
commit 75306178e1
38 changed files with 1158 additions and 6 deletions

View File

@ -112,6 +112,13 @@
BF0FA78F2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA78E2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift */; };
BF0FA7912DE16CBF00C9E5F2 /* VPSearchHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7902DE16CBF00C9E5F2 /* VPSearchHistoryView.swift */; };
BF0FA7942DE16E9300C9E5F2 /* JXTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7922DE16E9300C9E5F2 /* JXTagView.swift */; };
BF0FA7992DE1951A00C9E5F2 /* VPMyListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7982DE1951A00C9E5F2 /* VPMyListViewController.swift */; };
BF0FA79B2DE1984B00C9E5F2 /* VPCollectListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA79A2DE1984B00C9E5F2 /* VPCollectListViewController.swift */; };
BF0FA79D2DE198C600C9E5F2 /* VPWatchHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA79C2DE198C600C9E5F2 /* VPWatchHistoryViewController.swift */; };
BF0FA79F2DE1A29A00C9E5F2 /* VPCollectListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA79E2DE1A29A00C9E5F2 /* VPCollectListCell.swift */; };
BF0FA7A12DE1AA5100C9E5F2 /* VPTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7A02DE1AA5100C9E5F2 /* VPTableView.swift */; };
BF0FA7A32DE1AB1800C9E5F2 /* VPWatchHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7A22DE1AB1800C9E5F2 /* VPWatchHistoryCell.swift */; };
BF0FA7A52DE4384100C9E5F2 /* AttributedString+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7A42DE4384100C9E5F2 /* AttributedString+VPAdd.swift */; };
F939C04AD4003BA127F15C28 /* Pods_Veloria.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */; };
/* End PBXBuildFile section */
@ -230,6 +237,13 @@
BF0FA78E2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPUserDefaultsKey.swift; sourceTree = "<group>"; };
BF0FA7902DE16CBF00C9E5F2 /* VPSearchHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchHistoryView.swift; sourceTree = "<group>"; };
BF0FA7922DE16E9300C9E5F2 /* JXTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JXTagView.swift; sourceTree = "<group>"; };
BF0FA7982DE1951A00C9E5F2 /* VPMyListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPMyListViewController.swift; sourceTree = "<group>"; };
BF0FA79A2DE1984B00C9E5F2 /* VPCollectListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPCollectListViewController.swift; sourceTree = "<group>"; };
BF0FA79C2DE198C600C9E5F2 /* VPWatchHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPWatchHistoryViewController.swift; sourceTree = "<group>"; };
BF0FA79E2DE1A29A00C9E5F2 /* VPCollectListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPCollectListCell.swift; sourceTree = "<group>"; };
BF0FA7A02DE1AA5100C9E5F2 /* VPTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPTableView.swift; sourceTree = "<group>"; };
BF0FA7A22DE1AB1800C9E5F2 /* VPWatchHistoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPWatchHistoryCell.swift; sourceTree = "<group>"; };
BF0FA7A42DE4384100C9E5F2 /* AttributedString+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+VPAdd.swift"; sourceTree = "<group>"; };
E0BDA3570E00C90877E45AA0 /* Pods-VideoPlayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VideoPlayer.debug.xcconfig"; path = "Target Support Files/Pods-VideoPlayer/Pods-VideoPlayer.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -357,6 +371,7 @@
BF0FA76C2DE053C100C9E5F2 /* VPScrollView.swift */,
BF0FA7822DE1533E00C9E5F2 /* VPGradientLabel.swift */,
BF0FA7862DE1601200C9E5F2 /* VPTextField.swift */,
BF0FA7A02DE1AA5100C9E5F2 /* VPTableView.swift */,
);
path = View;
sourceTree = "<group>";
@ -413,6 +428,7 @@
BF0FA72F2DDEBB1600C9E5F2 /* UIButton+VPAdd.swift */,
BF0FA7402DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift */,
BF0FA77A2DE0788A00C9E5F2 /* UIStackView+VPAdd.swift */,
BF0FA7A42DE4384100C9E5F2 /* AttributedString+VPAdd.swift */,
);
path = Extension;
sourceTree = "<group>";
@ -714,8 +730,8 @@
BF0FA7952DE1948A00C9E5F2 /* MyList */ = {
isa = PBXGroup;
children = (
BF0FA7972DE1949B00C9E5F2 /* View */,
BF0FA7962DE1949300C9E5F2 /* Controller */,
BF0FA7972DE1949B00C9E5F2 /* View */,
);
path = MyList;
sourceTree = "<group>";
@ -723,6 +739,9 @@
BF0FA7962DE1949300C9E5F2 /* Controller */ = {
isa = PBXGroup;
children = (
BF0FA7982DE1951A00C9E5F2 /* VPMyListViewController.swift */,
BF0FA79A2DE1984B00C9E5F2 /* VPCollectListViewController.swift */,
BF0FA79C2DE198C600C9E5F2 /* VPWatchHistoryViewController.swift */,
);
path = Controller;
sourceTree = "<group>";
@ -730,6 +749,8 @@
BF0FA7972DE1949B00C9E5F2 /* View */ = {
isa = PBXGroup;
children = (
BF0FA79E2DE1A29A00C9E5F2 /* VPCollectListCell.swift */,
BF0FA7A22DE1AB1800C9E5F2 /* VPWatchHistoryCell.swift */,
);
path = View;
sourceTree = "<group>";
@ -879,6 +900,8 @@
BF0FA7792DE075FF00C9E5F2 /* VPSearchHomeView.swift in Sources */,
BF0FA7302DDEBB1600C9E5F2 /* UIButton+VPAdd.swift in Sources */,
BF0FA74C2DDF060200C9E5F2 /* VPVideoPlayerViewController.swift in Sources */,
BF0FA79B2DE1984B00C9E5F2 /* VPCollectListViewController.swift in Sources */,
BF0FA7A32DE1AB1800C9E5F2 /* VPWatchHistoryCell.swift in Sources */,
BF0FA76D2DE053C100C9E5F2 /* VPScrollView.swift in Sources */,
1B056E5D2DDACD8E007EE38D /* UIFont+VPAdd.swift in Sources */,
BF0FA7282DDC91F800C9E5F2 /* VPCollectionViewCell.swift in Sources */,
@ -887,6 +910,7 @@
BF0FA6F42DDC604500C9E5F2 /* VPHUD.swift in Sources */,
BF0FA7222DDC859D00C9E5F2 /* NSNumber+VPAdd.swift in Sources */,
BF0FA73B2DDED1C700C9E5F2 /* VPHomeListCell.swift in Sources */,
BF0FA79F2DE1A29A00C9E5F2 /* VPCollectListCell.swift in Sources */,
BF0FA7592DDF1C2800C9E5F2 /* VPPlayerProgressView.swift in Sources */,
BF0FA71B2DDC7FF200C9E5F2 /* VPImageView.swift in Sources */,
BF0FA7522DDF134700C9E5F2 /* VPVideoPlayerControlView.swift in Sources */,
@ -910,6 +934,7 @@
BF0FA74A2DDF04E200C9E5F2 /* VPPlayerProtocol.swift in Sources */,
BF0FA72C2DDD7B7300C9E5F2 /* VPHomeRankingContentCell.swift in Sources */,
BF0FA7942DE16E9300C9E5F2 /* JXTagView.swift in Sources */,
BF0FA7A52DE4384100C9E5F2 /* AttributedString+VPAdd.swift in Sources */,
BF0FA7652DE00A0E00C9E5F2 /* VPDetailPlayerControlView.swift in Sources */,
BF0FA7342DDEC74500C9E5F2 /* VPCategoryModel.swift in Sources */,
1B056E4F2DDAC7FC007EE38D /* VPNavigationController.swift in Sources */,
@ -917,11 +942,13 @@
BF0FA7202DDC83AE00C9E5F2 /* JXButton.swift in Sources */,
BF0FA7912DE16CBF00C9E5F2 /* VPSearchHistoryView.swift in Sources */,
BF0FA7072DDC687C00C9E5F2 /* VPHomeDataItem.swift in Sources */,
BF0FA7992DE1951A00C9E5F2 /* VPMyListViewController.swift in Sources */,
BF0FA7452DDF027900C9E5F2 /* VPPlayer.swift in Sources */,
BF0FA70E2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift in Sources */,
BF0FA6D72DDC5BE100C9E5F2 /* VPURLPath.swift in Sources */,
1B056E5B2DDACD80007EE38D /* UIColor+VPAdd.swift in Sources */,
BF0FA76B2DE0533400C9E5F2 /* VPEpisodeMenuView.swift in Sources */,
BF0FA79D2DE198C600C9E5F2 /* VPWatchHistoryViewController.swift in Sources */,
1B056E6A2DDAD0BF007EE38D /* VPLocalizedManager.swift in Sources */,
BF0FA7242DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift in Sources */,
BF0FA78F2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift in Sources */,
@ -936,6 +963,7 @@
1B056E722DDB022F007EE38D /* VPTabBarItem.swift in Sources */,
1B056E412DDAC30A007EE38D /* VPModel.swift in Sources */,
BF0FA70A2DDC69C800C9E5F2 /* VPTableViewCell.swift in Sources */,
BF0FA7A12DE1AA5100C9E5F2 /* VPTableView.swift in Sources */,
1B056E442DDAC355007EE38D /* UIDevice+VPAdd.swift in Sources */,
BF0FA7632DE006E700C9E5F2 /* VPDetailPlayerCell.swift in Sources */,
BF0FA73F2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift in Sources */,

View File

@ -88,8 +88,8 @@ extension VPTabBarController {
let nav1 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected"))
let nav2 = createNavigationController(viewController: VPExploreViewController(), title: "Explore".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected"))
let nav3 = createNavigationController(viewController: VPHomePageViewController(), title: "My List".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_03_selected"))
let nav4 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_04_selected"))
let nav3 = createNavigationController(viewController: VPMyListViewController(), title: "My List".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_03_selected"))
let nav4 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_04"), selectedImage: UIImage(named: "tabbar_icon_04_selected"))
viewControllers = [nav1, nav2, nav3, nav4]

View File

@ -0,0 +1,18 @@
//
// AttributedString+VPAdd.swift
// Veloria
//
// Created by on 2025/5/26.
//
import UIKit
extension AttributedString {
static func createAttributedString(string: String, color: UIColor, font: UIFont) -> AttributedString {
return AttributedString(string, attributes: AttributeContainer([
.foregroundColor: color,
.font : font
]))
}
}

View File

@ -62,7 +62,7 @@ class VPVideoAPI: NSObject {
}
///
static func requestCollectShort(isCollect: Bool, shortPlayId: String, videoId: String?, success: (() -> Void)?, failure: (() -> Void)? = nil) {
static func requestCollectShort(isCollect: Bool, shortPlayId: String, videoId: String?, isLoding: Bool = true, success: (() -> Void)?, failure: (() -> Void)? = nil) {
let path: String
if isCollect {
path = "/collect"
@ -79,7 +79,7 @@ class VPVideoAPI: NSObject {
}
var param = VPNetworkParameters(path: path)
param.isLoding = true
param.isLoding = isLoding
param.parameters = parameters
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<String>) in
@ -94,6 +94,51 @@ class VPVideoAPI: NSObject {
}
}
}
///
static func requestCollectList(page: Int, completer: ((_ listModel: VPListModel<VPShortModel>?) -> Void)?) {
var param = VPNetworkParameters(path: "/myCollections")
param.method = .get
param.parameters = [
"current_page" : page,
"page_size" : 20
]
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPListModel<VPShortModel>>) in
completer?(response.data)
}
}
///
static func requestCreatePlayHistory(videoId: String?, shortPlayId: String?) {
guard let shortPlayId = shortPlayId else { return }
var param = VPNetworkParameters(path: "/createHistory")
param.isLoding = false
param.isToast = false
param.parameters = [
"video_id" : videoId ?? "0",
"short_play_id" : shortPlayId
]
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<String>) in
}
}
///
static func requestPlayHistoryList(page: Int, pageSize: Int = 20, completer: ((_ listModel: VPListModel<VPShortModel>?) -> Void)?) {
var param = VPNetworkParameters(path: "/myHistorys")
param.method = .get
param.parameters = [
"current_page" : page,
"page_size" : pageSize
]
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPListModel<VPShortModel>>) in
completer?(response.data)
}
}
}
extension VPVideoAPI {

View File

@ -0,0 +1,52 @@
//
// VPTableView.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPTableView: UITableView {
var insetGroupedMargins: CGFloat = 15
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
separatorColor = .colorFFFFFF(alpha: 0.1)
separatorInset = .init(top: 0, left: 16, bottom: 0, right: 16)
self.backgroundColor = .clear
self.contentInsetAdjustmentBehavior = .never
if style == .insetGrouped {
sectionFooterHeight = 12
sectionHeaderHeight = 0.1
} else if style == .plain {
if #available(iOS 15.0, *) {
sectionHeaderTopPadding = 0
}
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// insetGrouped https://github.com/QMUI/QMUIDemo_iOS/blob/master/QMUI/QMUIKit/UIKitExtensions/UITableView%2BQMUI.m
override var layoutMargins: UIEdgeInsets {
set {
super.layoutMargins = newValue
}
get {
var margins = super.layoutMargins
if #available(iOS 13.0, *) {
if self.style == .insetGrouped {
margins.left = self.safeAreaInsets.left + insetGroupedMargins
margins.right = self.safeAreaInsets.right + insetGroupedMargins
}
}
return margins
}
}
}

View File

@ -0,0 +1,261 @@
//
// VPCollectListViewController.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPCollectListViewController: VPViewController {
var editState: VPMyListViewController.EditState = .normal {
didSet {
allSelectedButton.isHidden = editState == .normal
if editState == .normal {
collectionView.snp.updateConstraints { make in
make.top.equalToSuperview().offset(0)
}
isAllSelected = false
allSelectedButton.isSelected = false
} else {
collectionView.snp.updateConstraints { make in
make.top.equalToSuperview().offset(30)
}
}
self.collectionView.reloadData()
}
}
///
var isAllSelected: Bool {
set {
dataArr.forEach {
$0.vp_isSelected = newValue
}
self.collectionView.reloadData()
}
get {
if dataArr.count == 0 {
return false
}
var isAllSelected = true
for value in dataArr {
if value.vp_isSelected != true {
isAllSelected = false
break
}
}
return isAllSelected
}
}
///
var didChangeAllSelected: (() -> Void)?
private lazy var deleteGroup = DispatchGroup()
private lazy var dataArr: [VPShortModel] = []
private var page: Int = 1
private lazy var allSelectedButton: UIButton = {
var config = UIButton.Configuration.plain()
config.attributedTitle = AttributedString.createAttributedString(string: "Select All".localized, color: .colorFFFFFF(), font: .fontRegular(ofSize: 15))
config.imagePadding = 6
config.imagePlacement = .leading
config.baseBackgroundColor = .clear
config.contentInsets = .init(top: 5, leading: 0, bottom: 5, trailing: 0)
let button = UIButton(configuration: config)
button.isHidden = true
button.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
if button.isSelected {
button.configuration?.image = UIImage(named: "choice_icon_01_selected")
} else {
button.configuration?.image = UIImage(named: "choice_icon_01")
}
}
button.addTarget(self, action: #selector(handleAllSelectedButton), for: .touchUpInside)
return button
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let width = floor((UIScreen.width - 30 - 14) / 3)
let height = 148 / 110 * width + 31
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: width, height: height)
layout.minimumLineSpacing = 16
layout.minimumInteritemSpacing = 7
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
return layout
}()
private lazy var collectionView: VPCollectionView = {
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.contentInset = .init(top: 5, left: 0, bottom: 0, right: 0)
collectionView.vp_addRefreshHeader(insetTop: collectionView.top) { [weak self] in
self?.handleHeaderRefresh(nil)
}
collectionView.vp_addRefreshBackFooter { [weak self] in
self?.handleFooterRefresh(nil)
}
collectionView.register(VPCollectListCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.bgImageView.isHidden = true
self.view.backgroundColor = .clear
vp_setupUI()
requestDataArr(page: 1, completer: nil)
}
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
requestDataArr(page: 1) { [weak self] in
self?.collectionView.vp_endHeaderRefreshing()
}
}
override func handleFooterRefresh(_ completer: (() -> Void)?) {
requestDataArr(page: self.page + 1) { [weak self] in
self?.collectionView.vp_endFooterRefreshing()
}
}
func handleDelete() {
var count = 0
for value in dataArr {
if value.vp_isSelected == true {
count += 1
break
}
}
if count > 0 {
requestCancelCollect()
} else {
VPToast.show(text: "kToastText1")
}
}
}
extension VPCollectListViewController {
@objc private func handleAllSelectedButton() {
// let isSelected = !isAllSelected
isAllSelected = !isAllSelected
self.allSelectedButton.isSelected = isAllSelected
}
///
@objc private func updateAllSelectedButtonState() {
self.allSelectedButton.isSelected = isAllSelected
self.didChangeAllSelected?()
}
}
extension VPCollectListViewController {
private func vp_setupUI() {
view.addSubview(collectionView)
view.addSubview(allSelectedButton)
collectionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalToSuperview().offset(0)
}
allSelectedButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.top.equalToSuperview()
}
}
}
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
extension VPCollectListViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPCollectListCell
cell.model = dataArr[indexPath.row]
cell.editState = self.editState
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArr.count
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = dataArr[indexPath.row]
if editState == .normal {
let vc = VPDetailPlayerViewController()
vc.shortPlayId = model.short_play_id
navigationController?.pushViewController(vc, animated: true)
} else {
model.vp_isSelected = !(model.vp_isSelected ?? false)
collectionView.reloadData()
self.updateAllSelectedButtonState()
}
}
}
extension VPCollectListViewController {
private func requestDataArr(page: Int, completer: (() -> Void)?) {
VPVideoAPI.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()
if self.editState == .editing {
self.updateAllSelectedButtonState()
}
}
completer?()
}
}
private func requestCancelCollect() {
VPHUD.show()
self.dataArr.forEach {
if $0.vp_isSelected == true, let shortPlayId = $0.short_play_id {
self.deleteGroup.enter()
VPVideoAPI.requestCollectShort(isCollect: false, shortPlayId: shortPlayId, videoId: $0.short_play_video_id, isLoding: false) { [weak self] in
self?.deleteGroup.leave()
}
}
}
self.deleteGroup.notify(queue: .main) { [weak self] in
VPHUD.dismiss()
guard let self = self else { return }
self.requestDataArr(page: 1, completer: nil)
}
}
}

View File

@ -0,0 +1,171 @@
//
// VPMyListViewController.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPMyListViewController: VPViewController {
enum EditState {
case normal
case editing
}
///0 1
private var editState: EditState = .normal {
didSet {
if editState == .normal {
pageView.upSc.mainView.isHidden = false
pageView.upSc.dataView.isScrollEnabled = true
} else {
pageView.upSc.mainView.isHidden = true
pageView.upSc.dataView.isScrollEnabled = false
}
cancelEditButton.isHidden = editState == .normal
collectVC.editState = editState
editorButton.setNeedsUpdateConfiguration()
}
}
private lazy var collectVC = VPCollectListViewController()
private lazy var viewControllers: [UIViewController] = {
let vc2 = VPWatchHistoryViewController()
return [collectVC, vc2]
}()
private lazy var pageParam: WMZPageParam = {
let param = WMZPageParam()
param.wTitleArr = ["My List".localized, "History".localized]
param.wViewController = { [weak self] index in
return self?.viewControllers[index]
}
param.wMenuTitleColor = .colorFFFFFF(alpha: 0.3)
param.wMenuTitleSelectColor = .colorFFFFFF()
param.wMenuTitleUIFont = .fontMedium(ofSize: 15)
param.wMenuTitleSelectUIFont = .fontMedium(ofSize: 15)
param.wMenuHeight = 50
param.wMenuBgColor = .clear
param.wBgColor = .clear
param.wMenuTitleOffset = 25
param.wMenuCellMargin = 0
param.wMenuInsets = .init(top: 0, left: 15, bottom: 0, right: 15)
param.wCustomNaviBarY = { _ in
return 0
}
param.wCustomTabbarY = { _ in
return 0
}
param.wCustomDataViewTopOffset = 0
param.wEventEndTransferController = { [weak self] (oldVC, newVC, oldIndex, newIndex) in
guard let self = self else { return }
if newIndex == 1 {
self.editorButton.isHidden = true
self.editState = .normal
} else {
self.editorButton.isHidden = false
}
}
return param
}()
private lazy var pageView: WMZPageView = {
let y = UIScreen.statusBarHeight + 10
let height = UIScreen.height - y - UIScreen.customTabBarHeight
let pageView = WMZPageView(frame: .init(x: 0, y: y, width: UIScreen.width, height: height), param: pageParam, parentReponder: self)
pageView.backgroundColor = .clear
pageView.downSc?.backgroundColor = .clear
return pageView
}()
private lazy var editorButton: UIButton = {
var config = UIButton.Configuration.plain()
config.baseBackgroundColor = .colorFFFFFF(alpha: 0.05)
let button = UIButton(configuration: config)
button.layer.cornerRadius = 6
button.layer.masksToBounds = true
button.layer.borderWidth = 0.7
button.layer.borderColor = UIColor.colorFFFFFF(alpha: 0.1).cgColor
button.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
if self.editState == .normal {
button.configuration?.image = UIImage(named: "editor_icon_01")
} else {
button.configuration?.image = UIImage(named: "delete_icon_02")
}
}
button.addTarget(self, action: #selector(handleEditorButton), for: .touchUpInside)
return button
}()
private lazy var cancelEditButton: UIButton = {
let button = UIButton(type: .custom)
button.isHidden = true
button.setImage(UIImage(named: "cancel_icon_01"), for: .normal)
button.addTarget(self, action: #selector(handleCancelEditButton), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
vp_setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.editState = .normal
}
}
extension VPMyListViewController {
@objc private func handleEditorButton() {
if editState == .normal {
editState = .editing
} else {
self.collectVC.handleDelete()
}
}
@objc private func handleCancelEditButton() {
editState = .normal
}
}
extension VPMyListViewController {
private func vp_setupUI() {
view.addSubview(pageView)
view.addSubview(editorButton)
view.addSubview(cancelEditButton)
editorButton.snp.makeConstraints { make in
make.top.equalToSuperview().offset(UIScreen.statusBarHeight + 19)
make.right.equalToSuperview().offset(-15)
make.width.height.equalTo(32)
}
cancelEditButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.centerY.equalTo(editorButton)
}
}
}

View File

@ -0,0 +1,116 @@
//
// VPWatchHistoryViewController.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPWatchHistoryViewController: VPViewController {
private lazy var page = 1
private lazy var dataArr: [VPShortModel] = []
private lazy var tableView: VPTableView = {
let tableView = VPTableView(frame: .zero, style: .plain)
tableView.showsVerticalScrollIndicator = false
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
tableView.rowHeight = 118
tableView.vp_addRefreshHeader { [weak self] in
self?.handleHeaderRefresh(nil)
}
tableView.vp_addRefreshBackFooter { [weak self] in
self?.handleFooterRefresh(nil)
}
tableView.register(VPWatchHistoryCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.bgImageView.isHidden = true
self.view.backgroundColor = .clear
vp_setupUI()
requestDataArr(page: 1, completer: nil)
}
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
requestDataArr(page: 1) { [weak self] in
self?.tableView.vp_endHeaderRefreshing()
}
}
override func handleFooterRefresh(_ completer: (() -> Void)?) {
requestDataArr(page: self.page + 1) { [weak self] in
self?.tableView.vp_endFooterRefreshing()
}
}
}
extension VPWatchHistoryViewController {
private func vp_setupUI() {
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
//MARK: -------------- UITableViewDelegate UITableViewDataSource --------------
extension VPWatchHistoryViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! VPWatchHistoryCell
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 = dataArr[indexPath.row]
let vc = VPDetailPlayerViewController()
vc.shortPlayId = model.short_play_id
navigationController?.pushViewController(vc, animated: true)
}
}
extension VPWatchHistoryViewController {
private func requestDataArr(page: Int, completer: (() -> Void)?) {
VPVideoAPI.requestPlayHistoryList(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.tableView.reloadData()
}
completer?()
}
}
}

View File

@ -0,0 +1,85 @@
//
// VPCollectListCell.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPCollectListCell: VPCollectionViewCell {
var editState: VPMyListViewController.EditState = .normal {
didSet {
selectedView.isHidden = editState == .normal
}
}
var model: VPShortModel? {
didSet {
coverImageView.vp_setImage(url: model?.image_url)
videoNameLabel.text = model?.name
selectedView.isSelected = model?.vp_isSelected ?? false
}
}
private lazy var coverImageView: VPImageView = {
let imageView = VPImageView()
imageView.layer.cornerRadius = 10
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var videoNameLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 12)
label.textColor = .colorFFFFFF()
label.numberOfLines = 2
return label
}()
private lazy var selectedView: UIButton = {
let button = UIButton(type: .custom)
button.isUserInteractionEnabled = false
button.setImage(UIImage(named: "choice_icon_01"), for: .normal)
button.setImage(UIImage(named: "choice_icon_01_selected"), for: .selected)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPCollectListCell {
private func vp_setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(videoNameLabel)
coverImageView.addSubview(selectedView)
coverImageView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.bottom.equalToSuperview().offset(-31)
}
selectedView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(5)
make.right.equalToSuperview().offset(-5)
}
videoNameLabel.snp.makeConstraints { make in
make.left.equalToSuperview()
make.right.lessThanOrEqualToSuperview()
make.top.equalTo(coverImageView.snp.bottom).offset(4)
}
}
}

View File

@ -0,0 +1,186 @@
//
// VPWatchHistoryCell.swift
// Veloria
//
// Created by on 2025/5/24.
//
import UIKit
class VPWatchHistoryCell: VPTableViewCell {
var model: VPShortModel? {
didSet {
coverImageView.vp_setImage(url: model?.image_url)
videoNameLabel.text = model?.name
timeView.setNeedsUpdateConfiguration()
let epString = NSMutableAttributedString()
let currentEP = NSMutableAttributedString(string: String(format: "EP.%@".localized, model?.current_episode ?? ""))
currentEP.color = .color05CEA0()
epString.append(currentEP)
let totalEp = NSMutableAttributedString()
totalEp.appendString(" / ")
totalEp.appendString(String(format: "EP.%@".localized, "\(model?.episode_total ?? 0)"))
totalEp.color = .colorFFFFFF(alpha: 0.6)
epString.append(totalEp)
epLabel.attributedText = epString
updateCollectButtonState()
}
}
private lazy var coverImageView: VPImageView = {
let imageView = VPImageView()
imageView.layer.cornerRadius = 8
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var videoNameLabel: UILabel = {
let label = UILabel()
label.font = .fontMedium(ofSize: 14)
label.textColor = .colorFFFFFF()
label.numberOfLines = 2
return label
}()
private lazy var epLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
return label
}()
private lazy var collectButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "collect_icon_02"), for: .normal)
button.setImage(UIImage(named: "collect_icon_02_selected"), for: .selected)
button.setImage(UIImage(named: "collect_icon_02_selected"), for: [.selected, .highlighted])
button.setBackgroundImage(UIImage(color: .colorFFFFFF(alpha: 0.1)), for: .normal)
button.layer.cornerRadius = 6
button.layer.masksToBounds = true
button.addTarget(self, action: #selector(handleCollectButton), for: .touchUpInside)
return button
}()
private lazy var timeView: UIButton = {
var config = UIButton.Configuration.plain()
config.imagePlacement = .leading
config.imagePadding = 4
config.image = UIImage(named: "time_icon_01")
config.contentInsets = .zero
let view = UIButton(configuration: config)
view.isUserInteractionEnabled = false
view.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
var config = button.configuration
config?.attributedTitle = AttributedString.createAttributedString(string: self.model?.updated_at ?? "", color: .colorFFFFFF(alpha: 0.6), font: .fontRegular(ofSize: 10))
button.configuration = config
}
return view
}()
deinit {
NotificationCenter.default.removeObserver(self)
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
NotificationCenter.default.addObserver(self, selector: #selector(updateShortCollectStateNotification), name: VPVideoAPI.updateShortCollectStateNotification, object: nil)
vp_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPWatchHistoryCell {
@objc private func handleCollectButton() {
guard let shortPlayId = self.model?.short_play_id else { return }
guard let videoId = self.model?.short_play_video_id else { return }
let isCollect = !(self.model?.is_collect ?? false)
VPVideoAPI.requestCollectShort(isCollect: isCollect, shortPlayId: shortPlayId, videoId: videoId) { [weak self] in
guard let self = self else { return }
var count = self.model?.collect_total ?? 0
if isCollect {
count += 1
} else {
count -= 1
}
if count < 0 {
count = 0
}
self.model?.collect_total = count
}
}
@objc private func updateShortCollectStateNotification(sender: Notification) {
guard let userInfo = sender.userInfo else { return }
guard let shortPlayId = userInfo["id"] as? String else { return }
guard let isCollect = userInfo["state"] as? Bool else { return }
guard shortPlayId == self.model?.short_play_id else { return }
self.model?.is_collect = isCollect;
updateCollectButtonState()
}
private func updateCollectButtonState() {
self.collectButton.isSelected = self.model?.is_collect ?? false
}
}
extension VPWatchHistoryCell {
private func vp_setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(videoNameLabel)
contentView.addSubview(epLabel)
contentView.addSubview(collectButton)
contentView.addSubview(timeView)
coverImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15)
make.centerY.equalToSuperview()
make.top.equalToSuperview().offset(5)
make.width.equalTo(84)
}
videoNameLabel.snp.makeConstraints { make in
make.left.equalTo(coverImageView.snp.right).offset(12)
make.right.lessThanOrEqualToSuperview().offset(-75)
make.centerY.equalTo(coverImageView.snp.top).offset(21)
}
epLabel.snp.makeConstraints { make in
make.height.equalTo(26)
make.top.equalTo(coverImageView).offset(47)
make.left.equalTo(videoNameLabel)
}
collectButton.snp.makeConstraints { make in
make.width.height.equalTo(28)
make.centerY.equalToSuperview()
make.right.equalToSuperview().offset(-15)
}
timeView.snp.makeConstraints { make in
make.left.equalTo(videoNameLabel)
make.bottom.equalTo(coverImageView).offset(-4)
}
}
}

View File

@ -55,6 +55,14 @@ class VPDetailPlayerViewController: VPVideoPlayerViewController {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
override func play() {
guard let videoInfo = self.viewModel.currentPlayer?.videoInfo else { return }
super.play()
VPVideoAPI.requestCreatePlayHistory(videoId: videoInfo.short_play_video_id, shortPlayId: videoInfo.short_play_id)
}
}

View File

@ -13,7 +13,7 @@ import UIKit
var playerFinishHadle: (() -> Void)? { get set }
// var model: Any? { get set }
// var videoInfo: VPVideoInfoModel? { get set }
var videoInfo: VPVideoInfoModel? { get set }
var isCurrent: Bool { get set }

View File

@ -37,6 +37,7 @@ class VPShortModel: VPModel, SmartCodable {
var watch_total: Int?
var current_episode: String?
var video_url: String?
var updated_at: String?
@IgnoredKey
var titleAttributedString: NSAttributedString?

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: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

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: 958 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 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: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

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: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 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: 527 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

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: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 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: 593 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 863 B

View File

@ -18,9 +18,14 @@
"Search Results" = "Search Results";
"Recent Searches" = "Recent Searches";
"My List" = "My List";
"History" = "History";
"Select All" = "Select All";
"kHomeTitleText" = "10,000+ addictive shorts await!";
"kSearchPlaceholderText1" = "Search dramas";
"kSearchPlaceholderText2" = "#Recersal of fate";
"kHomeMenuTitle" = "Select Categories";
//请选择需要删除的短剧
"kToastText1" = "Please select the short plays that need to be deleted";