首次提交
This commit is contained in:
parent
78f4a215cc
commit
b89399a769
4
.gitignore
vendored
4
.gitignore
vendored
@ -14,6 +14,7 @@ xcuserdata/
|
|||||||
*.dSYM.zip
|
*.dSYM.zip
|
||||||
*.dSYM
|
*.dSYM
|
||||||
|
|
||||||
|
|
||||||
## Playgrounds
|
## Playgrounds
|
||||||
timeline.xctimeline
|
timeline.xctimeline
|
||||||
playground.xcworkspace
|
playground.xcworkspace
|
||||||
@ -38,7 +39,8 @@ playground.xcworkspace
|
|||||||
# you should judge for yourself, the pros and cons are mentioned at:
|
# you should judge for yourself, the pros and cons are mentioned at:
|
||||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||||
#
|
#
|
||||||
# Pods/
|
Pods/
|
||||||
|
Podfile.lock
|
||||||
#
|
#
|
||||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||||
# *.xcworkspace
|
# *.xcworkspace
|
||||||
|
|||||||
35
Podfile
Normal file
35
Podfile
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Uncomment the next line to define a global platform for your project
|
||||||
|
platform :ios, '13.0'
|
||||||
|
|
||||||
|
source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['ENABLE_BITCODE'] = 'NO'
|
||||||
|
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
|
||||||
|
config.build_settings['EXCLUDED_ARCHITECTURES'] = 'i386'
|
||||||
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
target 'SynthReel' do
|
||||||
|
use_frameworks!
|
||||||
|
|
||||||
|
pod 'YYCategories'
|
||||||
|
pod 'YYText'
|
||||||
|
pod 'Kingfisher'
|
||||||
|
pod 'SmartCodable'
|
||||||
|
pod 'Moya'
|
||||||
|
pod 'SVProgressHUD'
|
||||||
|
pod 'Toast'
|
||||||
|
pod 'JXSegmentedView'
|
||||||
|
pod 'JXPagingView/Paging'
|
||||||
|
pod 'FSPagerView'
|
||||||
|
pod 'JXPlayer', '~> 0.1.8'
|
||||||
|
pod 'MJRefresh'
|
||||||
|
pod 'collection-view-layouts/TagsLayout'
|
||||||
|
pod 'HWPanModal'
|
||||||
|
|
||||||
|
end
|
||||||
1091
SynthReel.xcodeproj/project.pbxproj
Normal file
1091
SynthReel.xcodeproj/project.pbxproj
Normal file
File diff suppressed because it is too large
Load Diff
7
SynthReel.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
SynthReel.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "2600"
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES"
|
||||||
|
buildArchitectures = "Automatic">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "03E9A7A62EC4716A000D1067"
|
||||||
|
BuildableName = "SynthReel.app"
|
||||||
|
BlueprintName = "SynthReel"
|
||||||
|
ReferencedContainer = "container:SynthReel.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "03E9A7A62EC4716A000D1067"
|
||||||
|
BuildableName = "SynthReel.app"
|
||||||
|
BlueprintName = "SynthReel"
|
||||||
|
ReferencedContainer = "container:SynthReel.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "03E9A7A62EC4716A000D1067"
|
||||||
|
BuildableName = "SynthReel.app"
|
||||||
|
BlueprintName = "SynthReel"
|
||||||
|
ReferencedContainer = "container:SynthReel.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
10
SynthReel.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
SynthReel.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:SynthReel.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
24
SynthReel.xcworkspace/xcshareddata/swiftpm/Package.resolved
Normal file
24
SynthReel.xcworkspace/xcshareddata/swiftpm/Package.resolved
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "0e62262c59a183f44748a161870cc0f2b76e1b0e46f648559704e4be9de523b9",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "estabbarcontroller",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/eggswift/ESTabBarController.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "93a30b833a05fd916c6d4c5d6e94a270cf3b6636",
|
||||||
|
"version" : "2.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "snapkit",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/SnapKit/SnapKit",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4",
|
||||||
|
"version" : "5.7.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
||||||
92
SynthReel/Base/API/SRHomeApi.swift
Normal file
92
SynthReel/Base/API/SRHomeApi.swift
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
//
|
||||||
|
// SRHomeApi.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/14.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Alamofire
|
||||||
|
|
||||||
|
|
||||||
|
struct SRHomeApi {
|
||||||
|
|
||||||
|
static func requestCategoryList() async -> [SRCategoryModel]? {
|
||||||
|
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
var param = SRNetwork.Parameters(path: "/getCategories")
|
||||||
|
param.method = .get
|
||||||
|
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRNetwork.List<SRCategoryModel>>) in
|
||||||
|
continuation.resume(returning: response.data?.list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static func requestHomeModulesData() async -> [SRHomeModuleItem]? {
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
var param = SRNetwork.Parameters(path: "/home/all-modules")
|
||||||
|
param.method = .get
|
||||||
|
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRNetwork.List<SRHomeModuleItem>>) in
|
||||||
|
continuation.resume(returning: response.data?.list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func requestCategoryVideoData(_ id: String, page: Int) async -> [SRShortModel]? {
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
var param = SRNetwork.Parameters(path: "/videoList")
|
||||||
|
param.method = .get
|
||||||
|
param.parameters = [
|
||||||
|
"category_id" : id,
|
||||||
|
"current_page" : page,
|
||||||
|
"page_size" : 20
|
||||||
|
]
|
||||||
|
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRNetwork.List<SRShortModel>>) in
|
||||||
|
continuation.resume(returning: response.data?.list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func requestHotSearchData() async -> [SRShortModel]? {
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
var param = SRNetwork.Parameters(path: "/search/hots")
|
||||||
|
param.method = .get
|
||||||
|
|
||||||
|
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRNetwork.List<SRShortModel>>) in
|
||||||
|
continuation.resume(returning: response.data?.list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func requestSearch(_ text: String) async -> [SRShortModel]? {
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
var param = SRNetwork.Parameters(path: "/search")
|
||||||
|
param.method = .get
|
||||||
|
param.parameters = [
|
||||||
|
"search" : text
|
||||||
|
]
|
||||||
|
|
||||||
|
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRNetwork.List<SRShortModel>>) in
|
||||||
|
continuation.resume(returning: response.data?.list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func requestHomeRecommendData(page: Int) async -> [SRShortModel]? {
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
var param = SRNetwork.Parameters(path: "/getRecommands")
|
||||||
|
param.method = .get
|
||||||
|
param.parameters = [
|
||||||
|
"page_size" : 20,
|
||||||
|
"current_page" : page
|
||||||
|
]
|
||||||
|
|
||||||
|
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRNetwork.List<SRShortModel>>) in
|
||||||
|
continuation.resume(returning: response.data?.list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
88
SynthReel/Base/API/SRShortApi.swift
Normal file
88
SynthReel/Base/API/SRShortApi.swift
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
//
|
||||||
|
// SRShortApi.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Alamofire
|
||||||
|
|
||||||
|
struct SRShortApi {
|
||||||
|
|
||||||
|
static func requestShortDetail(_ id: String) async -> (SRShortDetailModel?, Int?, String?) {
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
var param = SRNetwork.Parameters(path: "/getVideoDetails")
|
||||||
|
param.method = .get
|
||||||
|
param.parameters = [
|
||||||
|
"short_play_id" : id,
|
||||||
|
"video_id" : 0,
|
||||||
|
]
|
||||||
|
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRShortDetailModel>) in
|
||||||
|
if response.isSuccess {
|
||||||
|
continuation.resume(returning:(response.data, response.code, response.msg))
|
||||||
|
} else {
|
||||||
|
continuation.resume(returning:(nil, response.code, response.msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func requestShortCollect(shortId: String, videoId: String?, isCollect: Bool) async -> Bool {
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
var path = ""
|
||||||
|
if isCollect {
|
||||||
|
path = "/collect"
|
||||||
|
} else {
|
||||||
|
path = "/cancelCollect"
|
||||||
|
}
|
||||||
|
|
||||||
|
var param = SRNetwork.Parameters(path: path)
|
||||||
|
param.isLoding = true
|
||||||
|
param.parameters = [
|
||||||
|
"short_play_id" : shortId,
|
||||||
|
"video_id" : videoId ?? "0",
|
||||||
|
]
|
||||||
|
|
||||||
|
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRShortDetailModel>) in
|
||||||
|
if response.isSuccess {
|
||||||
|
continuation.resume(returning: true)
|
||||||
|
NotificationCenter.default.post(name: SRShortApi.updateShortCollectStateNotification, object: nil, userInfo: [
|
||||||
|
"state" : isCollect,
|
||||||
|
"id" : shortId,
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
continuation.resume(returning: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func requestCreatePlayHistory(shortId: String?, videoId: String?) async {
|
||||||
|
guard let shortId = shortId else { return }
|
||||||
|
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
var param = SRNetwork.Parameters(path: "/createHistory")
|
||||||
|
param.isToast = false
|
||||||
|
param.parameters = [
|
||||||
|
"short_play_id" : shortId,
|
||||||
|
"video_id" : videoId ?? "0",
|
||||||
|
]
|
||||||
|
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRShortDetailModel>) in
|
||||||
|
continuation.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension SRShortApi {
|
||||||
|
|
||||||
|
|
||||||
|
///更新短剧关注状态 [ "state" : isCollect, "id" : shortPlayId,]
|
||||||
|
static let updateShortCollectStateNotification = NSNotification.Name(rawValue: "SRShortApi.updateShortCollectStateNotification")
|
||||||
|
|
||||||
|
}
|
||||||
25
SynthReel/Base/API/SRUserApi.swift
Normal file
25
SynthReel/Base/API/SRUserApi.swift
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// SRUserApi.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Alamofire
|
||||||
|
|
||||||
|
|
||||||
|
struct SRUserApi {
|
||||||
|
|
||||||
|
static func requestUserInfo() async -> SRUserInfo? {
|
||||||
|
|
||||||
|
await withCheckedContinuation { continuation in
|
||||||
|
var param = SRNetwork.Parameters(path: "/customer/info")
|
||||||
|
param.method = .get
|
||||||
|
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRUserInfo>) in
|
||||||
|
continuation.resume(returning: response.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
SynthReel/Base/Define/SRDefine.swift
Normal file
24
SynthReel/Base/Define/SRDefine.swift
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// SRDefine.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
///app版本号
|
||||||
|
let kSRAPPVersion: String = (Bundle.main.infoDictionary!["CFBundleShortVersionString"] as? String) ?? "0"
|
||||||
|
let kSRAPPBundleVersion: String = (Bundle.main.infoDictionary!["CFBundleVersion"] as? String) ?? "0"
|
||||||
|
|
||||||
|
let kSRAPPBundleName: String = (Bundle.main.infoDictionary!["CFBundleName"] as? String) ?? ""
|
||||||
|
let kSRAPPName: String = (Bundle.main.infoDictionary!["CFBundleDisplayName"] as? String) ?? ""
|
||||||
|
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
public func srPrint(message: Any? , file: String = #file, function: String = #function, line: Int = #line) {
|
||||||
|
print("\n\(Date(timeIntervalSinceNow: 8 * 60 * 60)) \(file.components(separatedBy: "/").last ?? "") \(function) \(line): \(message ?? "")")
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
public func srPrint(message: Any?) { }
|
||||||
|
#endif
|
||||||
14
SynthReel/Base/Define/SRUserDefaultsKey.swift
Normal file
14
SynthReel/Base/Define/SRUserDefaultsKey.swift
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// SRUserDefaultsKey.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
///登录token
|
||||||
|
let kSRAccountTokenDefaultsKey = "kSRAccountTokenDefaultsKey"
|
||||||
|
|
||||||
|
///用户信息
|
||||||
|
let kSRUserInfoDefaultsKey = "kSRUserInfoDefaultsKey"
|
||||||
43
SynthReel/Base/Extension/NSNumber+SRAdd.swift
Normal file
43
SynthReel/Base/Extension/NSNumber+SRAdd.swift
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// NSNumber+SRAdd.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension NSNumber {
|
||||||
|
|
||||||
|
func toString(maximumFractionDigits: Int = 10, minimumFractionDigits: Int? = nil, roundingMode: NumberFormatter.RoundingMode? = nil) -> String {
|
||||||
|
let formatter = NumberFormatter()
|
||||||
|
formatter.minimumIntegerDigits = 1
|
||||||
|
formatter.maximumFractionDigits = maximumFractionDigits
|
||||||
|
if let minimumFractionDigits = minimumFractionDigits {
|
||||||
|
formatter.minimumFractionDigits = minimumFractionDigits
|
||||||
|
}
|
||||||
|
if let roundingMode = roundingMode {
|
||||||
|
formatter.roundingMode = roundingMode
|
||||||
|
}
|
||||||
|
formatter.numberStyle = .none
|
||||||
|
return formatter.string(from: self) ?? "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Int {
|
||||||
|
|
||||||
|
func formatTimeGroup() -> (String, String, String) {
|
||||||
|
let seconds = self
|
||||||
|
|
||||||
|
var s: String = "00"
|
||||||
|
var m: String = "00"
|
||||||
|
var h: String = "00"
|
||||||
|
s = String(format: "%02d", Int(Int(seconds) % 60))
|
||||||
|
m = String(format: "%02d", Int(seconds / 60) % 60)
|
||||||
|
h = String(format: "%02d", Int(seconds / 3600))
|
||||||
|
|
||||||
|
return (h, m, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
26
SynthReel/Base/Extension/String+SRAdd.swift
Normal file
26
SynthReel/Base/Extension/String+SRAdd.swift
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// String+SRAdd.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import YYCategories
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
|
||||||
|
var localized: String {
|
||||||
|
return String(localized: LocalizationValue(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
func localizedReplace(text: String) -> String {
|
||||||
|
return self.localized.replacingOccurrences(of: "##", with: text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func size(_ font: UIFont, _ size: CGSize) -> CGSize {
|
||||||
|
return (self as NSString).size(for: font, size: size, mode: .byWordWrapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
18
SynthReel/Base/Extension/UIFont+SRAdd.swift
Normal file
18
SynthReel/Base/Extension/UIFont+SRAdd.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// UIFont+SRAdd.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
|
||||||
|
extension UIFont {
|
||||||
|
|
||||||
|
static func font(ofSize: CGFloat, weight: Weight) -> UIFont {
|
||||||
|
return .systemFont(ofSize: ofSize, weight: weight)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
50
SynthReel/Base/Extension/UIScreen+SRAdd.swift
Normal file
50
SynthReel/Base/Extension/UIScreen+SRAdd.swift
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// UIScreen+SRAdd.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension UIScreen {
|
||||||
|
|
||||||
|
static var screen: UIScreen {
|
||||||
|
return UIScreen.main
|
||||||
|
}
|
||||||
|
|
||||||
|
static var width: CGFloat {
|
||||||
|
return UIScreen.main.bounds.width
|
||||||
|
}
|
||||||
|
|
||||||
|
static var height: CGFloat {
|
||||||
|
return UIScreen.main.bounds.height
|
||||||
|
}
|
||||||
|
|
||||||
|
static var safeTop: CGFloat {
|
||||||
|
return SRTool.keyWindow?.safeAreaInsets.top ?? 20
|
||||||
|
}
|
||||||
|
|
||||||
|
static var safeBottom: CGFloat {
|
||||||
|
return SRTool.keyWindow?.safeAreaInsets.bottom ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static var navBarHeight: CGFloat {
|
||||||
|
return safeTop + 44
|
||||||
|
}
|
||||||
|
|
||||||
|
static var tabBarHeight: CGFloat {
|
||||||
|
return safeBottom + 49
|
||||||
|
}
|
||||||
|
|
||||||
|
///屏幕宽比
|
||||||
|
static var widthRatio: CGFloat {
|
||||||
|
return UIScreen.width / 375
|
||||||
|
}
|
||||||
|
|
||||||
|
static func getRatioWidth(size: CGFloat) -> CGFloat {
|
||||||
|
return self.widthRatio * size
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
46
SynthReel/Base/Extension/UIScrollView+SRAdd.swift
Normal file
46
SynthReel/Base/Extension/UIScrollView+SRAdd.swift
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// UIScrollView+SRAdd.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import MJRefresh
|
||||||
|
|
||||||
|
extension UIScrollView {
|
||||||
|
|
||||||
|
func sr_addRefreshHeader(insetTop: CGFloat = 0, block: (() -> Void)?) {
|
||||||
|
|
||||||
|
self.mj_header = MJRefreshNormalHeader(refreshingBlock: {
|
||||||
|
block?()
|
||||||
|
})
|
||||||
|
self.mj_header?.ignoredScrollViewContentInsetTop = insetTop
|
||||||
|
}
|
||||||
|
|
||||||
|
func sr_addRefreshFooter(insetBottom: CGFloat = 0, block: (() -> Void)?) {
|
||||||
|
self.mj_footer = MJRefreshBackNormalFooter(refreshingBlock: {
|
||||||
|
block?()
|
||||||
|
})
|
||||||
|
self.mj_footer?.ignoredScrollViewContentInsetBottom = insetBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func sr_endHeaderRefreshing() {
|
||||||
|
self.mj_header?.endRefreshing()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sr_endFooterRefreshing() {
|
||||||
|
if self.mj_footer?.state == .noMoreData { return }
|
||||||
|
self.mj_footer?.endRefreshing()
|
||||||
|
}
|
||||||
|
|
||||||
|
///重置没有更多
|
||||||
|
func sr_resetNoMoreData() {
|
||||||
|
self.mj_footer?.resetNoMoreData()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sr_endRefreshingWithNoMoreData() {
|
||||||
|
self.mj_footer?.endRefreshingWithNoMoreData()
|
||||||
|
}
|
||||||
|
}
|
||||||
22
SynthReel/Base/Extension/UIStackView+SRAdd.swift
Normal file
22
SynthReel/Base/Extension/UIStackView+SRAdd.swift
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// UIStackView+SRAdd.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/15.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension UIStackView {
|
||||||
|
|
||||||
|
func sr_removeAllArrangedSubview() {
|
||||||
|
let arrangedSubviews = self.arrangedSubviews
|
||||||
|
|
||||||
|
arrangedSubviews.forEach {
|
||||||
|
self.removeArrangedSubview($0)
|
||||||
|
$0.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
92
SynthReel/Base/Extension/UIView+SRAdd.swift
Normal file
92
SynthReel/Base/Extension/UIView+SRAdd.swift
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
//
|
||||||
|
// UIView+SRAdd.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension UIView {
|
||||||
|
|
||||||
|
///绘制正六边形
|
||||||
|
func applyHexagonMask(_ cornerRadius: CGFloat = 0) {
|
||||||
|
let width = bounds.width
|
||||||
|
let height = bounds.height
|
||||||
|
let center = CGPoint(x: width / 2, y: height / 2)
|
||||||
|
|
||||||
|
let radius = max(width, height) / 2
|
||||||
|
|
||||||
|
let path = UIBezierPath()
|
||||||
|
|
||||||
|
// 六边形 6 个点(平顶)
|
||||||
|
var points: [CGPoint] = []
|
||||||
|
for i in 0..<6 {
|
||||||
|
let angle = CGFloat(i) * (.pi / 3)
|
||||||
|
let x = center.x + radius * cos(angle)
|
||||||
|
let y = center.y + radius * sin(angle)
|
||||||
|
points.append(CGPoint(x: x, y: y))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建带圆角的六边形
|
||||||
|
for i in 0..<6 {
|
||||||
|
let prev = points[(i + 5) % 6] // 上一个点
|
||||||
|
let curr = points[i] // 当前点
|
||||||
|
let next = points[(i + 1) % 6] // 下一个点
|
||||||
|
|
||||||
|
// 当前点方向的两条边的单位向量
|
||||||
|
let v1 = CGPoint(x: curr.x - prev.x, y: curr.y - prev.y)
|
||||||
|
let v2 = CGPoint(x: curr.x - next.x, y: curr.y - next.y)
|
||||||
|
|
||||||
|
let l1 = sqrt(v1.x * v1.x + v1.y * v1.y)
|
||||||
|
let l2 = sqrt(v2.x * v2.x + v2.y * v2.y)
|
||||||
|
|
||||||
|
let u1 = CGPoint(x: v1.x / l1, y: v1.y / l1)
|
||||||
|
let u2 = CGPoint(x: v2.x / l2, y: v2.y / l2)
|
||||||
|
|
||||||
|
// 两边分别往内缩 cornerRadius
|
||||||
|
let p1 = CGPoint(x: curr.x - u1.x * cornerRadius,
|
||||||
|
y: curr.y - u1.y * cornerRadius)
|
||||||
|
let p2 = CGPoint(x: curr.x - u2.x * cornerRadius,
|
||||||
|
y: curr.y - u2.y * cornerRadius)
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
path.move(to: p1)
|
||||||
|
} else {
|
||||||
|
path.addLine(to: p1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 两个偏移点之间加圆角弧线
|
||||||
|
path.addQuadCurve(to: p2, controlPoint: curr)
|
||||||
|
}
|
||||||
|
|
||||||
|
path.close()
|
||||||
|
|
||||||
|
// 设置 mask
|
||||||
|
let maskLayer = CAShapeLayer()
|
||||||
|
maskLayer.path = path.cgPath
|
||||||
|
layer.mask = maskLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func addHollowHole(holePath: UIBezierPath, dimColor: UIColor = UIColor.black.withAlphaComponent(0.6)) {
|
||||||
|
// 1. 整个遮罩
|
||||||
|
let fullPath = UIBezierPath(rect: self.bounds)
|
||||||
|
|
||||||
|
// 2. 挖洞
|
||||||
|
fullPath.append(holePath)
|
||||||
|
fullPath.usesEvenOddFillRule = true
|
||||||
|
|
||||||
|
// 3. 蒙层 layer
|
||||||
|
let shapeLayer = CAShapeLayer()
|
||||||
|
shapeLayer.path = fullPath.cgPath
|
||||||
|
shapeLayer.fillRule = .evenOdd
|
||||||
|
shapeLayer.fillColor = dimColor.cgColor // 半透明遮罩
|
||||||
|
shapeLayer.opacity = 1.0
|
||||||
|
|
||||||
|
// self.layer.sublayers?.forEach { $0.removeFromSuperlayer() } // 如需避免重复叠加
|
||||||
|
// self.layer.addSublayer(shapeLayer)
|
||||||
|
self.layer.mask = shapeLayer
|
||||||
|
}
|
||||||
|
}
|
||||||
46
SynthReel/Base/Extension/UserDefaults+SRAdd.swift
Normal file
46
SynthReel/Base/Extension/UserDefaults+SRAdd.swift
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// UserDefaults+SRAdd.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
extension UserDefaults {
|
||||||
|
|
||||||
|
static func sr_setObject(_ obj: NSSecureCoding?, forKey key: String) {
|
||||||
|
let defaults = UserDefaults.standard
|
||||||
|
|
||||||
|
guard let obj = obj else {
|
||||||
|
defaults.removeObject(forKey: key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let data = try NSKeyedArchiver.archivedData(withRootObject: obj, requiringSecureCoding: true)
|
||||||
|
defaults.set(data, forKey: key)
|
||||||
|
} catch {
|
||||||
|
print("Error archiving object: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func sr_object<T: NSObject & NSSecureCoding>(forKey key: String, as type: T.Type) -> T? {
|
||||||
|
let defaults = UserDefaults.standard
|
||||||
|
|
||||||
|
guard let data = defaults.data(forKey: key) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let object = try NSKeyedUnarchiver.unarchivedObject(ofClass: type, from: data)
|
||||||
|
return object
|
||||||
|
} catch {
|
||||||
|
print("Error unarchiving object: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
193
SynthReel/Base/Networking/SRNetwork.swift
Normal file
193
SynthReel/Base/Networking/SRNetwork.swift
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
//
|
||||||
|
// SRNetwork.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Moya
|
||||||
|
import SmartCodable
|
||||||
|
|
||||||
|
class SRNetwork: NSObject {
|
||||||
|
|
||||||
|
private static let operationQueue = OperationQueue()
|
||||||
|
private static var tokenOperation: BlockOperation?
|
||||||
|
|
||||||
|
static let provider = MoyaProvider<SRTargetType>()
|
||||||
|
|
||||||
|
|
||||||
|
static func request<T>(parameters: SRNetwork.Parameters, completion: ((_ response: SRNetwork.Response<T>) -> Void)?) {
|
||||||
|
|
||||||
|
if SRAccountManager.manager.token == nil {
|
||||||
|
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: SRNetwork.Response<T>) in
|
||||||
|
semaphore.signal()
|
||||||
|
completion?(response)
|
||||||
|
}
|
||||||
|
semaphore.wait()
|
||||||
|
}
|
||||||
|
///设置依赖关系
|
||||||
|
requestOperation.addDependency(tokenOperation)
|
||||||
|
|
||||||
|
operationQueue.addOperation(requestOperation)
|
||||||
|
} else {
|
||||||
|
_request(parameters: parameters, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
static func _request<T>(parameters: SRNetwork.Parameters, completion: ((_ response: SRNetwork.Response<T>) -> Void)?) -> Cancellable {
|
||||||
|
|
||||||
|
if parameters.isLoding {
|
||||||
|
SRHud.show()
|
||||||
|
}
|
||||||
|
return provider.request(.request(parameters: parameters)) { result in
|
||||||
|
if parameters.isLoding {
|
||||||
|
SRHud.dismiss()
|
||||||
|
}
|
||||||
|
guard let completion = completion else {return}
|
||||||
|
_resultDispose(parameters: parameters, result: result, completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func _resultDispose<T>(parameters: SRNetwork.Parameters, result: Result<Moya.Response, MoyaError>, completion: ((_ response: SRNetwork.Response<T>) -> Void)?) {
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let response):
|
||||||
|
let code = response.statusCode
|
||||||
|
if code == 401 || code == 402 || code == 403 {
|
||||||
|
|
||||||
|
if parameters.path == "/customer/register" {
|
||||||
|
var res = SRNetwork.Response<T>()
|
||||||
|
res.code = -1
|
||||||
|
if parameters.isToast {
|
||||||
|
// BRToast.show(text: "Error".localized)
|
||||||
|
}
|
||||||
|
completion?(res)
|
||||||
|
} else {
|
||||||
|
// if code == 402, parameters.isToast {
|
||||||
|
// BRToast.show(text: "beereel_network_error_1".localized)
|
||||||
|
// }
|
||||||
|
///重新获取token
|
||||||
|
self.requestToken { token in
|
||||||
|
if token != nil {
|
||||||
|
_Concurrency.Task {
|
||||||
|
await SRAccountManager.manager.updateUserInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///将请求失败数据重新请求
|
||||||
|
if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" {
|
||||||
|
|
||||||
|
let requestOperation = BlockOperation {
|
||||||
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
_request(parameters: parameters) { (response: SRNetwork.Response<T>) in
|
||||||
|
semaphore.signal()
|
||||||
|
completion?(response)
|
||||||
|
}
|
||||||
|
semaphore.wait()
|
||||||
|
}
|
||||||
|
///设置依赖关系
|
||||||
|
requestOperation.addDependency(tokenOperation)
|
||||||
|
|
||||||
|
operationQueue.addOperation(requestOperation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let tempData = try response.mapString()
|
||||||
|
srPrint(message: parameters.parameters)
|
||||||
|
srPrint(message: parameters.path)
|
||||||
|
|
||||||
|
let response: SRNetwork.Response<T> = _deserialize(data: tempData)
|
||||||
|
if !response.isSuccess{
|
||||||
|
if parameters.isToast {
|
||||||
|
// BRToast.show(text: response.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completion?(response)
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
var res = SRNetwork.Response<T>()
|
||||||
|
res.code = -1
|
||||||
|
if parameters.isToast {
|
||||||
|
// BRToast.show(text: "Error".localized)
|
||||||
|
}
|
||||||
|
completion?(res)
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
srPrint(message: error)
|
||||||
|
var res = SRNetwork.Response<T>()
|
||||||
|
res.code = -1
|
||||||
|
if parameters.isToast {
|
||||||
|
// BRToast.show(text: "beereel_network".localized)
|
||||||
|
}
|
||||||
|
completion?(res)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
///解析数据
|
||||||
|
static private func _deserialize<T>(data: String) -> SRNetwork.Response<T> {
|
||||||
|
var response: SRNetwork.Response<T>?
|
||||||
|
|
||||||
|
let decrypted = SRResponseCryptor.decrypt(data: data)
|
||||||
|
srPrint(message: decrypted)
|
||||||
|
response = SRNetwork.Response<T>.deserialize(from: decrypted)
|
||||||
|
response?.rawData = decrypted
|
||||||
|
|
||||||
|
if let response = response {
|
||||||
|
return response
|
||||||
|
} else {
|
||||||
|
var response = SRNetwork.Response<T>()
|
||||||
|
response.code = -1
|
||||||
|
response.msg = "Error".localized
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRNetwork {
|
||||||
|
///获取token
|
||||||
|
static func requestToken(completer: ((_ token: SRAccountToken?) -> Void)?) {
|
||||||
|
guard self.tokenOperation == nil else {
|
||||||
|
completer?(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tokenOperation = BlockOperation(block: {
|
||||||
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
let param = SRNetwork.Parameters(path: "/customer/register")
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRAccountToken>) in
|
||||||
|
if let token = response.data {
|
||||||
|
SRAccountManager.manager.setAccountToken(token)
|
||||||
|
}
|
||||||
|
do { semaphore.signal() }
|
||||||
|
self.tokenOperation = nil
|
||||||
|
completer?(response.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
semaphore.wait()
|
||||||
|
})
|
||||||
|
operationQueue.addOperation(self.tokenOperation!)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
62
SynthReel/Base/Networking/SRNetworkModel.swift
Normal file
62
SynthReel/Base/Networking/SRNetworkModel.swift
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// SRNetworkModel.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SmartCodable
|
||||||
|
import Moya
|
||||||
|
import Alamofire
|
||||||
|
|
||||||
|
|
||||||
|
extension SRNetwork {
|
||||||
|
|
||||||
|
struct Parameters {
|
||||||
|
var baseURL: URL?
|
||||||
|
var parameters: [String : Any]?
|
||||||
|
var method: Moya.Method = .post
|
||||||
|
var path: String
|
||||||
|
var isLoding: Bool = false
|
||||||
|
var isToast: Bool = true
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Response<T : SmartCodable>: SmartCodable {
|
||||||
|
|
||||||
|
var code: Int?
|
||||||
|
var data: T?
|
||||||
|
var msg: String?
|
||||||
|
|
||||||
|
@SmartIgnored
|
||||||
|
var rawData: Any?
|
||||||
|
|
||||||
|
var isSuccess: Bool {
|
||||||
|
return code == 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct List<T: SmartCodable>: SmartCodable {
|
||||||
|
var list: [T]?
|
||||||
|
var pagination: Pagination?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Pagination: SmartCodable {
|
||||||
|
var current_page: Int?
|
||||||
|
var page_size: Int?
|
||||||
|
var page_total: Int?
|
||||||
|
var total_size: Int?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//extension String: SmartCodable {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//extension Int: SmartCodable {
|
||||||
|
//
|
||||||
|
//}
|
||||||
75
SynthReel/Base/Networking/SRNetworkReachableManager.swift
Normal file
75
SynthReel/Base/Networking/SRNetworkReachableManager.swift
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// SRNetworkReachableManager.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Network
|
||||||
|
|
||||||
|
|
||||||
|
class SRNetworkReachableManager: NSObject {
|
||||||
|
|
||||||
|
static let manager = SRNetworkReachableManager()
|
||||||
|
///是否有网
|
||||||
|
var isReachable: Bool?
|
||||||
|
|
||||||
|
private var connectionType: NWInterface.InterfaceType?
|
||||||
|
private var status: NWPath.Status?
|
||||||
|
|
||||||
|
private let monitor = NWPathMonitor()
|
||||||
|
private let queue = DispatchQueue(label: "NetworkMonitorQueue")
|
||||||
|
|
||||||
|
func startMonitoring() {
|
||||||
|
|
||||||
|
monitor.pathUpdateHandler = { [weak self] path in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.status = path.status
|
||||||
|
|
||||||
|
if path.usesInterfaceType(.wifi) {
|
||||||
|
self.connectionType = .wifi
|
||||||
|
} else if path.usesInterfaceType(.cellular) {
|
||||||
|
self.connectionType = .cellular
|
||||||
|
} else if path.usesInterfaceType(.wiredEthernet) {
|
||||||
|
self.connectionType = .wiredEthernet
|
||||||
|
} else {
|
||||||
|
self.connectionType = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let agoReachable = self.isReachable
|
||||||
|
|
||||||
|
if path.status == .satisfied, self.connectionType != nil {
|
||||||
|
self.isReachable = true
|
||||||
|
if agoReachable == false {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: SRNetworkReachableManager.networkStatusDidChangeNotification, object: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
self.isReachable = false
|
||||||
|
if agoReachable == true {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
NotificationCenter.default.post(name: SRNetworkReachableManager.networkStatusDidChangeNotification, object: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
monitor.start(queue: queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopMonitoring() {
|
||||||
|
monitor.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRNetworkReachableManager {
|
||||||
|
///网络发生变化
|
||||||
|
@objc static let networkStatusDidChangeNotification = NSNotification.Name(rawValue: "SRNetworkReachableManager.networkStatusDidChangeNotification")
|
||||||
|
}
|
||||||
95
SynthReel/Base/Networking/SRResponseCryptor.swift
Normal file
95
SynthReel/Base/Networking/SRResponseCryptor.swift
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
//
|
||||||
|
// SRResponseCryptor.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
struct SRResponseCryptor {
|
||||||
|
|
||||||
|
static func decrypt(data: String) -> String {
|
||||||
|
guard data.hasPrefix("$") else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
let decryptedData = deStrBytes(data: data)
|
||||||
|
return String(data: decryptedData, encoding: .utf8) ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
static func deStrBytes(data: String) -> Data {
|
||||||
|
let hexData = String(data.dropFirst())
|
||||||
|
var bytes = Data()
|
||||||
|
|
||||||
|
var index = hexData.startIndex
|
||||||
|
while index < hexData.endIndex {
|
||||||
|
let nextIndex = hexData.index(index, offsetBy: 2, limitedBy: hexData.endIndex) ?? hexData.endIndex
|
||||||
|
let byteString = String(hexData[index..<nextIndex])
|
||||||
|
|
||||||
|
if let byte = UInt8(byteString, radix: 16) {
|
||||||
|
bytes.append(byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
index = nextIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
return de(data: bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密数据
|
||||||
|
static func de(data: Data) -> Data {
|
||||||
|
guard !data.isEmpty else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
let saltLen = Int(data[data.startIndex])
|
||||||
|
guard data.count >= 1 + saltLen else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
let salt = data.subdata(in: 1..<1+saltLen)
|
||||||
|
let encryptedData = data.subdata(in: 1+saltLen..<data.count)
|
||||||
|
|
||||||
|
return deWithSalt(data: encryptedData, salt: salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用盐值解密数据
|
||||||
|
static func deWithSalt(data: Data, salt: Data) -> Data {
|
||||||
|
let decryptedData = cxEd(data: data)
|
||||||
|
return removeSalt(data: decryptedData, salt: salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密/解密数据(按位取反)
|
||||||
|
static func cxEd(data: Data) -> Data {
|
||||||
|
return Data(data.map { $0 ^ 0xFF })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从数据中移除盐值
|
||||||
|
static func removeSalt(data: Data, salt: Data) -> Data {
|
||||||
|
guard !salt.isEmpty else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = Data()
|
||||||
|
let saltBytes = [UInt8](salt)
|
||||||
|
let saltCount = saltBytes.count
|
||||||
|
|
||||||
|
for (index, byte) in data.enumerated() {
|
||||||
|
let saltByte = saltBytes[index % saltCount]
|
||||||
|
let decryptedByte = calRemoveSalt(v: byte, s: saltByte)
|
||||||
|
result.append(decryptedByte)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算移除盐值后的字节
|
||||||
|
static func calRemoveSalt(v: UInt8, s: UInt8) -> UInt8 {
|
||||||
|
if v >= s {
|
||||||
|
return v - s
|
||||||
|
} else {
|
||||||
|
return UInt8(0xFF) - (s - v) + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
91
SynthReel/Base/Networking/SRTargetType.swift
Normal file
91
SynthReel/Base/Networking/SRTargetType.swift
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
//
|
||||||
|
// SRTargetType.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SmartCodable
|
||||||
|
import Moya
|
||||||
|
import Alamofire
|
||||||
|
import AdSupport
|
||||||
|
import YYCategories
|
||||||
|
|
||||||
|
enum SRTargetType {
|
||||||
|
case request(parameters: SRNetwork.Parameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRTargetType: TargetType {
|
||||||
|
var baseURL: URL {
|
||||||
|
return .init(string: SRBaseURL)!
|
||||||
|
}
|
||||||
|
|
||||||
|
var path: String {
|
||||||
|
switch self {
|
||||||
|
case .request(let param):
|
||||||
|
return SRUrlPathPrefix + param.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var method: Moya.Method {
|
||||||
|
switch self {
|
||||||
|
case .request(let param):
|
||||||
|
return param.method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var task: Moya.Task {
|
||||||
|
switch self {
|
||||||
|
case .request(let param):
|
||||||
|
let parameters = param.parameters ?? [:]
|
||||||
|
return .requestParameters(parameters: parameters, encoding: getEncoding())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var headers: [String : String]? {
|
||||||
|
var dic: [String : String] = [
|
||||||
|
"system-version" : UIDevice.current.systemVersion,
|
||||||
|
"lang-key" : "en",
|
||||||
|
"idfa" : ASIdentifierManager.shared().advertisingIdentifier.uuidString,
|
||||||
|
"time-zone" : SRTargetType.timeZone(), //时区
|
||||||
|
"brand" : "apple", //品牌
|
||||||
|
"app-version" : kSRAPPVersion,
|
||||||
|
"app-name" : "SynthReel",
|
||||||
|
"device-id" : SRDeviceId.shared.id, //设备id
|
||||||
|
"system-type" : "ios",
|
||||||
|
"model" : UIDevice.current.machineModelName ?? "",
|
||||||
|
"authorization" : SRAccountManager.manager.token?.token ?? "",
|
||||||
|
"device-gaid" : UIDevice.current.identifierForVendor?.uuidString ?? "",
|
||||||
|
"product-prefix" : "SynthReel"
|
||||||
|
]
|
||||||
|
#if DEBUG
|
||||||
|
dic["security"] = "false"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return dic
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension SRTargetType {
|
||||||
|
|
||||||
|
var sampleData: Data { return "".data(using: String.Encoding.utf8)! }
|
||||||
|
|
||||||
|
func getEncoding() -> ParameterEncoding {
|
||||||
|
switch self.method {
|
||||||
|
case .get, .delete:
|
||||||
|
return URLEncoding.default
|
||||||
|
default:
|
||||||
|
return JSONEncoding.default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func timeZone() -> String {
|
||||||
|
let timeZone = NSTimeZone.local as NSTimeZone
|
||||||
|
let timeZoneSecondsFromGMT = timeZone.secondsFromGMT / 3600
|
||||||
|
return String(format: "GMT+0%d:00", timeZoneSecondsFromGMT)
|
||||||
|
}
|
||||||
|
}
|
||||||
27
SynthReel/Base/Networking/SRUrlPath.swift
Normal file
27
SynthReel/Base/Networking/SRUrlPath.swift
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// SRUrlPath.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
let SRBaseURL = "https://api-hbqinjiu.hbqinjiu.com"
|
||||||
|
let SRUrlPathPrefix = "/eon"
|
||||||
|
let SRWebBaseURL = "https://www.hbqinjiu.com"
|
||||||
|
let SRCampaignWebURL = "https://campaign.hbqinjiu.com"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
SynthReel
|
||||||
|
admin-api https://admin-api-synthreeltv.synthreeltv.com
|
||||||
|
api https://api-synthreeltv.synthreeltv.com/th
|
||||||
|
|
||||||
|
|
||||||
|
官网:https://www.synthreeltv.com
|
||||||
|
协议页面:https://www.synthreeltv.com/xxxxx
|
||||||
|
反馈:https://campaign.synthreeltv.com/pages/leave/index 传{theme:theme_16}
|
||||||
|
注销:https://campaign.synthreeltv.com/pages/setting/logout 传参{theme: theme_11, device-id:xxxx}
|
||||||
|
w2a:https://w2a.synthreeltv.com/
|
||||||
|
Api:https://api-synthreeltv.synthreeltv.com/th
|
||||||
|
*/
|
||||||
24
SynthReel/Base/View/SRCollectionView.swift
Normal file
24
SynthReel/Base/View/SRCollectionView.swift
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// SRCollectionView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/14.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRCollectionView: UICollectionView {
|
||||||
|
|
||||||
|
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
|
||||||
|
super.init(frame: frame, collectionViewLayout: layout)
|
||||||
|
self.backgroundColor = .clear
|
||||||
|
self.contentInsetAdjustmentBehavior = .never
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
45
SynthReel/Base/View/SRGradientView.swift
Normal file
45
SynthReel/Base/View/SRGradientView.swift
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// SRGradientView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/15.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRGradientView: UIView {
|
||||||
|
|
||||||
|
override class var layerClass: AnyClass {
|
||||||
|
return CAGradientLayer.self
|
||||||
|
}
|
||||||
|
|
||||||
|
var gradientLayer: CAGradientLayer {
|
||||||
|
return self.layer as! CAGradientLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
var locations: [NSNumber]? {
|
||||||
|
didSet {
|
||||||
|
self.gradientLayer.locations = locations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors: [CGColor]? {
|
||||||
|
didSet {
|
||||||
|
self.gradientLayer.colors = colors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var startPoint: CGPoint = .zero {
|
||||||
|
didSet {
|
||||||
|
self.gradientLayer.startPoint = startPoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var endPoint: CGPoint = .zero {
|
||||||
|
didSet {
|
||||||
|
self.gradientLayer.endPoint = endPoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
100
SynthReel/Base/View/SRImageView.swift
Normal file
100
SynthReel/Base/View/SRImageView.swift
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
//
|
||||||
|
// SRImageView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/15.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
class SRImageView: UIImageView {
|
||||||
|
|
||||||
|
var placeholderColor = UIColor.gray
|
||||||
|
var placeholderImage = UIImage(named: "placeholder_image")
|
||||||
|
|
||||||
|
private lazy var placeholderImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: placeholderImage)
|
||||||
|
imageView.isHidden = true
|
||||||
|
imageView.contentMode = .scaleAspectFit
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(image: UIImage?) {
|
||||||
|
super.init(image: image)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(image: UIImage?, highlightedImage: UIImage?) {
|
||||||
|
super.init(image: image, highlightedImage: highlightedImage)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func awakeFromNib() {
|
||||||
|
super.awakeFromNib()
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func _init() {
|
||||||
|
self.contentMode = .scaleAspectFill
|
||||||
|
self.layer.masksToBounds = true
|
||||||
|
if image == nil {
|
||||||
|
self.backgroundColor = self.placeholderColor
|
||||||
|
placeholderImageView.isHidden = false
|
||||||
|
}
|
||||||
|
addSubview(placeholderImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
override var image: UIImage? {
|
||||||
|
didSet {
|
||||||
|
if self.backgroundColor == nil && image == nil {
|
||||||
|
self.backgroundColor = self.placeholderColor
|
||||||
|
} else if image != nil {
|
||||||
|
if self.backgroundColor == self.placeholderColor {
|
||||||
|
self.backgroundColor = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if image == nil {
|
||||||
|
placeholderImageView.isHidden = false
|
||||||
|
} else {
|
||||||
|
placeholderImageView.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
placeholderImageView.frame = .init(x: 0, y: 0, width: self.bounds.width * (2 / 3), height: self.bounds.height * (2 / 3))
|
||||||
|
placeholderImageView.center = .init(x: self.bounds.width / 2, y: self.bounds.height / 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension UIImageView {
|
||||||
|
func sr_setImage(_ url: String?, placeholder: UIImage? = nil, completer: ((_ image: UIImage?, _ url: URL?) -> Void)? = nil) {
|
||||||
|
|
||||||
|
self.kf.setImage(with: URL(string: url ?? ""), placeholder: placeholder, options: nil) { result in
|
||||||
|
switch result {
|
||||||
|
case .success(let value):
|
||||||
|
completer?(value.image, value.source.url)
|
||||||
|
default :
|
||||||
|
completer?(nil, nil)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
SynthReel/Base/View/SRLabel.swift
Normal file
55
SynthReel/Base/View/SRLabel.swift
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// SRLabel.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/14.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import YYText
|
||||||
|
|
||||||
|
class SRLabel: UILabel {
|
||||||
|
|
||||||
|
var textColors: [CGColor]?
|
||||||
|
var textStartPoint: CGPoint?
|
||||||
|
var textEndPoint: CGPoint?
|
||||||
|
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
let size = self.bounds.size
|
||||||
|
if let text = self.text, text.count > 0, let colors = self.textColors, let startPoint = self.textStartPoint, let endPoine = self.textEndPoint {
|
||||||
|
self.textColor = UIColor(patternImage: UIImage.sr_getGradientImage(size: size, colors: colors, startPoint: startPoint, endPoint: endPoine))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIImage {
|
||||||
|
|
||||||
|
static func sr_getGradientImage(size: CGSize, colors: [CGColor], startPoint: CGPoint, endPoint: CGPoint) -> UIImage{
|
||||||
|
|
||||||
|
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
|
||||||
|
guard let context = UIGraphicsGetCurrentContext() else{return UIImage()}
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
///设置渐变颜色
|
||||||
|
let gradientRef = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: nil)!
|
||||||
|
let startPoint = CGPoint(x: size.width * startPoint.x, y: size.height * startPoint.y)
|
||||||
|
let endPoint = CGPoint(x: size.width * endPoint.x, y: size.height * endPoint.y)
|
||||||
|
context.drawLinearGradient(gradientRef, start: startPoint, end: endPoint, options: CGGradientDrawingOptions(arrayLiteral: .drawsBeforeStartLocation,.drawsAfterEndLocation))
|
||||||
|
let gradientImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
return gradientImage ?? UIImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sr_resized(to size: CGSize) -> UIImage {
|
||||||
|
let renderer = UIGraphicsImageRenderer(size: size)
|
||||||
|
return renderer.image { _ in
|
||||||
|
self.draw(in: CGRect(origin: .zero, size: size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
84
SynthReel/Base/View/SRPanModalContentView.swift
Normal file
84
SynthReel/Base/View/SRPanModalContentView.swift
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// SRPanModalContentView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import HWPanModal
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRPanModalContentView: HWPanModalContentView {
|
||||||
|
|
||||||
|
|
||||||
|
var contentHeight = UIScreen.height * (2 / 3)
|
||||||
|
|
||||||
|
///更新UI contentSize发生变化时调用
|
||||||
|
func setNeedsLayoutUpdate() {
|
||||||
|
self.panModalSetNeedsLayoutUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var bgImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "pan_bg_image_01"))
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
addSubview(bgImageView)
|
||||||
|
|
||||||
|
bgImageView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: HWPanModalPresentable
|
||||||
|
override func panScrollable() -> UIScrollView? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
override func longFormHeight() -> PanModalHeight {
|
||||||
|
return PanModalHeightMake(.content, contentHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func showDragIndicator() -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func backgroundConfig() -> HWBackgroundConfig {
|
||||||
|
let config = HWBackgroundConfig()
|
||||||
|
config.backgroundAlpha = 0.6
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
override func allowsTapBackgroundToDismiss() -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override func allowsDragToDismiss() -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func allowsPullDownWhenShortState() -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func showsScrollableVerticalScrollIndicator() -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func springDamping() -> CGFloat {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override func cornerRadius() -> CGFloat {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
21
SynthReel/Base/View/SRScrollView.swift
Normal file
21
SynthReel/Base/View/SRScrollView.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// SRScrollView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/15.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRScrollView: UIScrollView {
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
self.contentInsetAdjustmentBehavior = .never
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
}
|
||||||
|
}
|
||||||
49
SynthReel/Base/View/SRTableView.swift
Normal file
49
SynthReel/Base/View/SRTableView.swift
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// SRTableView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRTableView: UITableView {
|
||||||
|
|
||||||
|
var insetGroupedMargins: CGFloat = 15
|
||||||
|
|
||||||
|
override init(frame: CGRect, style: UITableView.Style) {
|
||||||
|
super.init(frame: frame, style: style)
|
||||||
|
separatorInset = .init(top: 0, left: 15, bottom: 0, right: 15)
|
||||||
|
self.backgroundColor = .clear
|
||||||
|
self.contentInsetAdjustmentBehavior = .never
|
||||||
|
|
||||||
|
if style == .insetGrouped || style == .grouped {
|
||||||
|
sectionFooterHeight = 14
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
override var layoutMargins: UIEdgeInsets {
|
||||||
|
set {
|
||||||
|
super.layoutMargins = newValue
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
var margins = super.layoutMargins
|
||||||
|
if self.style == .insetGrouped {
|
||||||
|
margins.left = self.safeAreaInsets.left + insetGroupedMargins
|
||||||
|
margins.right = self.safeAreaInsets.right + insetGroupedMargins
|
||||||
|
}
|
||||||
|
return margins
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
48
SynthReel/Base/View/SRTableViewCell.swift
Normal file
48
SynthReel/Base/View/SRTableViewCell.swift
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
//
|
||||||
|
// SRTableViewCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
_init()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func awakeFromNib() {
|
||||||
|
super.awakeFromNib()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||||
|
super.setSelected(selected, animated: animated)
|
||||||
|
|
||||||
|
// Configure the view for the selected state
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func _init() {
|
||||||
|
self.layer.rasterizationScale = UIScreen.main.scale
|
||||||
|
self.layer.shouldRasterize = true
|
||||||
|
self.selectionStyle = .none
|
||||||
|
self.backgroundColor = .clear
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UITableViewCell {
|
||||||
|
|
||||||
|
var fa_tableView: UITableView? {
|
||||||
|
return self.value(forKey: "_tableView") as? UITableView
|
||||||
|
}
|
||||||
|
}
|
||||||
36
SynthReel/Base/ViewController/SRNavigationController.swift
Normal file
36
SynthReel/Base/ViewController/SRNavigationController.swift
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// SRNavigationController.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRNavigationController: UINavigationController {
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
// Do any additional setup after loading the view.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
|
||||||
|
if children.count > 0 {
|
||||||
|
viewController.hidesBottomBarWhenPushed = true
|
||||||
|
}
|
||||||
|
super.pushViewController(viewController, animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) {
|
||||||
|
for (index, value) in viewControllers.enumerated() {
|
||||||
|
if index != 0 {
|
||||||
|
value.hidesBottomBarWhenPushed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.setViewControllers(viewControllers, animated: animated)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
51
SynthReel/Base/ViewController/SRTabBarController.swift
Normal file
51
SynthReel/Base/ViewController/SRTabBarController.swift
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// SRTabBarController.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import ESTabBarController
|
||||||
|
|
||||||
|
class SRTabBarController: ESTabBarController {
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
view.backgroundColor = ._010101
|
||||||
|
|
||||||
|
let nav1 = createNavigationView(SRHomeViewController(), image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected"))
|
||||||
|
let nav2 = createNavigationView(SRRecommendPlayerViewController(), image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected"))
|
||||||
|
let nav3 = createNavigationView(SRMyShortViewController(), image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_03_selected"))
|
||||||
|
let nav4 = createNavigationView(SRViewController(), image: UIImage(named: "tabbar_icon_04"), selectedImage: UIImage(named: "tabbar_icon_04_selected"))
|
||||||
|
|
||||||
|
|
||||||
|
viewControllers = [nav1, nav2, nav3, nav4]
|
||||||
|
|
||||||
|
let appearance = UITabBarAppearance()
|
||||||
|
appearance.backgroundColor = ._010101
|
||||||
|
appearance.shadowColor = .clear
|
||||||
|
appearance.backgroundImage = UIImage()
|
||||||
|
appearance.shadowImage = UIImage()
|
||||||
|
|
||||||
|
self.tabBar.standardAppearance = appearance
|
||||||
|
self.tabBar.scrollEdgeAppearance = appearance
|
||||||
|
self.tabBar.isTranslucent = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func createNavigationView(_ viewController: UIViewController, image: UIImage?, selectedImage: UIImage?) -> UINavigationController {
|
||||||
|
|
||||||
|
let contentView = ESTabBarItemContentView()
|
||||||
|
contentView.itemContentMode = .alwaysOriginal
|
||||||
|
contentView.renderingMode = .alwaysOriginal
|
||||||
|
|
||||||
|
let tabBarItem = ESTabBarItem(contentView, image: image, selectedImage: selectedImage)
|
||||||
|
|
||||||
|
let nav = SRNavigationController(rootViewController: viewController)
|
||||||
|
nav.tabBarItem = tabBarItem
|
||||||
|
return nav
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
82
SynthReel/Base/ViewController/SRViewController.swift
Normal file
82
SynthReel/Base/ViewController/SRViewController.swift
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
//
|
||||||
|
// SRViewController.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
import JXPagingView
|
||||||
|
|
||||||
|
class SRViewController: UIViewController {
|
||||||
|
|
||||||
|
var didScrollCallback: ((_ : UIScrollView) -> Void)?
|
||||||
|
|
||||||
|
lazy var backgroundImageView = UIImageView(image: UIImage(named: "background_image_01"))
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
self.edgesForExtendedLayout = [.top]
|
||||||
|
self.view.backgroundColor = ._010101
|
||||||
|
|
||||||
|
view.addSubview(backgroundImageView)
|
||||||
|
|
||||||
|
backgroundImageView.snp.makeConstraints { make in
|
||||||
|
make.left.right.top.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHeaderRefresh(_ completer: (() -> Void)?) {
|
||||||
|
completer?()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleFooterRefresh(_ completer: (() -> Void)?) {
|
||||||
|
completer?()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: JXPagingSmoothViewListViewDelegate
|
||||||
|
extension SRViewController: JXPagingSmoothViewListViewDelegate, JXPagingViewListViewDelegate {
|
||||||
|
func listViewDidScrollCallback(callback: @escaping (UIScrollView) -> ()) {
|
||||||
|
self.didScrollCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
func listView() -> UIView {
|
||||||
|
return self.view
|
||||||
|
}
|
||||||
|
|
||||||
|
func listScrollView() -> UIScrollView {
|
||||||
|
return UIScrollView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIViewController {
|
||||||
|
// func configNavigationBack(_ imageName: String = "Frame 3011") {
|
||||||
|
// let image = UIImage(named: imageName)
|
||||||
|
//
|
||||||
|
// let leftBarButtonItem = UIBarButtonItem(image: image, style: .plain ,target: self,action: #selector(handleNavigationBack))
|
||||||
|
// navigationItem.leftBarButtonItem = leftBarButtonItem
|
||||||
|
// }
|
||||||
|
|
||||||
|
@objc func sr_handleNavigationBack() {
|
||||||
|
self.sr_toLastViewController(animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sr_toLastViewController(animated: Bool) {
|
||||||
|
if self.navigationController != nil
|
||||||
|
{
|
||||||
|
if self.navigationController?.viewControllers.count == 1
|
||||||
|
{
|
||||||
|
self.dismiss(animated: animated, completion: nil)
|
||||||
|
} else {
|
||||||
|
self.navigationController?.popViewController(animated: animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if self.presentingViewController != nil {
|
||||||
|
self.dismiss(animated: animated, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
SynthReel/Class/Home/M/SRCategoryModel.swift
Normal file
16
SynthReel/Class/Home/M/SRCategoryModel.swift
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// SRCategoryModel.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/14.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SmartCodable
|
||||||
|
|
||||||
|
struct SRCategoryModel: SmartCodable {
|
||||||
|
|
||||||
|
var id: String?
|
||||||
|
var name: String?
|
||||||
|
}
|
||||||
56
SynthReel/Class/Home/M/SRHomeModuleItem.swift
Normal file
56
SynthReel/Class/Home/M/SRHomeModuleItem.swift
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// SRHomeModuleItem.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/14.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SmartCodable
|
||||||
|
|
||||||
|
class SRHomeModuleItem: NSObject, SmartCodable {
|
||||||
|
|
||||||
|
required override init() { }
|
||||||
|
|
||||||
|
enum ModuleKey: String, SmartCaseDefaultable {
|
||||||
|
case banner = "home_banner"
|
||||||
|
///猜你喜欢
|
||||||
|
case detailsRecommand = "get_details_recommand"
|
||||||
|
case popular = "home_v3_recommand"
|
||||||
|
case updates = "week_ranking"
|
||||||
|
case bingeWorthy = "week_highest_recommend"
|
||||||
|
case viralHits = "highest_payment_hot_video"
|
||||||
|
case premiereNow = "new_recommand"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var title: String?
|
||||||
|
var module_key: ModuleKey?
|
||||||
|
var list: [SRShortModel] = []
|
||||||
|
|
||||||
|
@SmartAny
|
||||||
|
var data: Any?
|
||||||
|
|
||||||
|
|
||||||
|
func didFinishMapping() {
|
||||||
|
if let data = data as? [[String : Any]] {
|
||||||
|
self.list = [SRShortModel].deserialize(from: data) ?? []
|
||||||
|
} else if let data = data as? [String : Any] {
|
||||||
|
var dataList: [[String : Any]]?
|
||||||
|
if let list = data["list"] as? [[String : Any]] {
|
||||||
|
self.title = data["title"] as? String
|
||||||
|
dataList = list
|
||||||
|
|
||||||
|
} else if let list = data["shortPlayList"] as? [[String : Any]] {
|
||||||
|
self.title = data["category_name"] as? String
|
||||||
|
dataList = list
|
||||||
|
}
|
||||||
|
|
||||||
|
if let dataList = dataList {
|
||||||
|
self.list = [SRShortModel].deserialize(from: dataList) ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
42
SynthReel/Class/Home/V/SRHomeBannerCell.swift
Normal file
42
SynthReel/Class/Home/V/SRHomeBannerCell.swift
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// SRHomeBannerCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/15.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import FSPagerView
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeBannerCell: FSPagerViewCell {
|
||||||
|
|
||||||
|
var model: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
if let image = model?.horizontally_img, image.count > 0 {
|
||||||
|
coverImageView.sr_setImage(image)
|
||||||
|
} else {
|
||||||
|
coverImageView.sr_setImage(model?.image_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var coverImageView: SRImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
contentView.addSubview(coverImageView)
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
40
SynthReel/Class/Home/V/SRHomeBannerMiniCell.swift
Normal file
40
SynthReel/Class/Home/V/SRHomeBannerMiniCell.swift
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// SRHomeBannerMiniCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeBannerMiniCell: UICollectionViewCell {
|
||||||
|
|
||||||
|
var model: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sr_setImage(model?.image_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var coverImageView: SRImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
imageView.layer.cornerRadius = 2
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
contentView.addSubview(coverImageView)
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
177
SynthReel/Class/Home/V/SRHomeBannerView.swift
Normal file
177
SynthReel/Class/Home/V/SRHomeBannerView.swift
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
//
|
||||||
|
// SRHomeBannerView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/15.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
import FSPagerView
|
||||||
|
|
||||||
|
class SRHomeBannerView: UIView {
|
||||||
|
|
||||||
|
var dataArr: [SRShortModel]? {
|
||||||
|
didSet {
|
||||||
|
self.bannerView.reloadData()
|
||||||
|
self.miniCollectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var didSelectedShort: ((_ model: SRShortModel?) -> Void)?
|
||||||
|
|
||||||
|
lazy var bgImageView = UIImageView(image: UIImage(named: "home_banner_bg"))
|
||||||
|
|
||||||
|
lazy var bannerView: FSPagerView = {
|
||||||
|
let view = FSPagerView()
|
||||||
|
view.delegate = self
|
||||||
|
view.dataSource = self
|
||||||
|
view.isInfinite = true
|
||||||
|
view.automaticSlidingInterval = 5
|
||||||
|
view.register(SRHomeBannerCell.self, forCellWithReuseIdentifier: "cell")
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
// lazy var miniBgView: UIView = {
|
||||||
|
// let view = UIView()
|
||||||
|
// view.layer.cornerRadius = 4
|
||||||
|
// view.layer.masksToBounds = true
|
||||||
|
// view.backgroundColor = ._051_B_22.withAlphaComponent(0.5)
|
||||||
|
// return view
|
||||||
|
// }()
|
||||||
|
|
||||||
|
lazy var miniCollectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.itemSize = .init(width: 36, height: 46)
|
||||||
|
layout.minimumLineSpacing = 4
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var miniCollectionView: SRCollectionView = {
|
||||||
|
let collectionView = SRCollectionView(frame: .zero, collectionViewLayout: miniCollectionViewLayout)
|
||||||
|
collectionView.layer.cornerRadius = 4
|
||||||
|
collectionView.layer.masksToBounds = true
|
||||||
|
collectionView.backgroundColor = ._051_B_22.withAlphaComponent(0.5)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.showsVerticalScrollIndicator = false
|
||||||
|
collectionView.contentInset = .init(top: 5, left: 0, bottom: 5, right: 0)
|
||||||
|
collectionView.register(SRHomeBannerMiniCell.self, forCellWithReuseIdentifier: "cell")
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
applyHeteromorphicLayer()
|
||||||
|
}
|
||||||
|
|
||||||
|
///添加异形切边
|
||||||
|
func applyHeteromorphicLayer() {
|
||||||
|
let viewSize = bannerView.bounds.size
|
||||||
|
let path = UIBezierPath()
|
||||||
|
let layer = CAShapeLayer()
|
||||||
|
|
||||||
|
//右上角边长
|
||||||
|
let topRight = UIScreen.getRatioWidth(size: 25)
|
||||||
|
//左下角边长
|
||||||
|
let bottomLeft = UIScreen.getRatioWidth(size: 10)
|
||||||
|
|
||||||
|
path.move(to: .init(x: 0, y: 0))
|
||||||
|
path.addLine(to: .init(x: viewSize.width - topRight, y: 0))
|
||||||
|
path.addLine(to: .init(x: viewSize.width, y: topRight))
|
||||||
|
path.addLine(to: .init(x: viewSize.width, y: viewSize.height))
|
||||||
|
path.addLine(to: .init(x: bottomLeft, y: viewSize.height))
|
||||||
|
path.addLine(to: .init(x: 0, y: viewSize.height - bottomLeft))
|
||||||
|
path.close()
|
||||||
|
|
||||||
|
layer.path = path.cgPath
|
||||||
|
bannerView.layer.mask = layer
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHomeBannerView {
|
||||||
|
|
||||||
|
func sr_setupUI() {
|
||||||
|
let bgImage = self.bgImageView.image!
|
||||||
|
let bgWidth = UIScreen.width - 30
|
||||||
|
let bgHeight = bgImage.size.height / bgImage.size.width * bgWidth
|
||||||
|
|
||||||
|
addSubview(bgImageView)
|
||||||
|
addSubview(bannerView)
|
||||||
|
addSubview(miniCollectionView)
|
||||||
|
|
||||||
|
bgImageView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
make.height.equalTo(bgHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
bannerView.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(bgImageView).offset(11)
|
||||||
|
make.right.equalTo(bgImageView).offset(-11)
|
||||||
|
make.top.equalTo(bgImageView).offset(18)
|
||||||
|
make.bottom.equalTo(bgImageView).offset(-12)
|
||||||
|
}
|
||||||
|
|
||||||
|
miniCollectionView.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(bannerView).offset(7)
|
||||||
|
make.centerY.equalTo(bannerView)
|
||||||
|
make.top.equalTo(bannerView).offset(7)
|
||||||
|
make.width.equalTo(46)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: FSPagerViewDelegate FSPagerViewDataSource
|
||||||
|
extension SRHomeBannerView: FSPagerViewDelegate, FSPagerViewDataSource {
|
||||||
|
func numberOfItems(in pagerView: FSPagerView) -> Int {
|
||||||
|
return self.dataArr?.count ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func pagerView(_ pagerView: FSPagerView, cellForItemAt index: Int) -> FSPagerViewCell {
|
||||||
|
let cell = pagerView.dequeueReusableCell(withReuseIdentifier: "cell", at: index) as! SRHomeBannerCell
|
||||||
|
cell.model = self.dataArr?[index]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func pagerView(_ pagerView: FSPagerView, didSelectItemAt index: Int) {
|
||||||
|
self.didSelectedShort?(self.dataArr?[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UICollectionViewDelegate UICollectionViewDataSource
|
||||||
|
extension SRHomeBannerView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SRHomeBannerMiniCell
|
||||||
|
cell.model = self.dataArr?[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return self.dataArr?.count ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
if indexPath.row == self.bannerView.currentIndex { return }
|
||||||
|
|
||||||
|
self.bannerView.selectItem(at: indexPath.row, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
135
SynthReel/Class/Home/V/SRHomeBingeWorthyCell.swift
Normal file
135
SynthReel/Class/Home/V/SRHomeBingeWorthyCell.swift
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
//
|
||||||
|
// SRHomeBingeWorthyCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeBingeWorthyCell: UICollectionViewCell {
|
||||||
|
|
||||||
|
var model: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sr_setImage(model?.image_url)
|
||||||
|
titleLabel.text = model?.name
|
||||||
|
desLabel.text = model?.sr_description
|
||||||
|
if let text = model?.category?.first, text.count > 0 {
|
||||||
|
categoryLabel.text = "#\(text)"
|
||||||
|
} else {
|
||||||
|
categoryLabel.text = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var bgImageView = UIImageView(image: UIImage(named: "home_binge_worthy_cell_image"))
|
||||||
|
|
||||||
|
lazy var coverImageView: SRImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
imageView.layer.cornerRadius = 4
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 14, weight: .medium)
|
||||||
|
label.textColor = .white
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var categoryLabel: SRLabel = {
|
||||||
|
let label = SRLabel()
|
||||||
|
label.font = .font(ofSize: 11, weight: .regular)
|
||||||
|
label.textColors = [UIColor.srGreen.cgColor, UIColor.srBlue.cgColor]
|
||||||
|
label.textStartPoint = .init(x: 0.5, y: 0)
|
||||||
|
label.textEndPoint = .init(x: 0.5, y: 1)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var desLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 11, weight: .regular)
|
||||||
|
label.textColor = .A_6_A_6_A_6
|
||||||
|
label.numberOfLines = 3
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var playImageView = UIImageView(image: UIImage(named: "play_icon_01"))
|
||||||
|
|
||||||
|
lazy var playLabel: UILabel = {
|
||||||
|
let label = SRLabel()
|
||||||
|
label.font = .font(ofSize: 11, weight: .regular)
|
||||||
|
label.textColors = [UIColor.srGreen.cgColor, UIColor.srBlue.cgColor]
|
||||||
|
label.textStartPoint = .init(x: 0.5, y: 0)
|
||||||
|
label.textEndPoint = .init(x: 0.5, y: 1)
|
||||||
|
label.text = "Play"
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHomeBingeWorthyCell {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
contentView.addSubview(bgImageView)
|
||||||
|
contentView.addSubview(coverImageView)
|
||||||
|
contentView.addSubview(titleLabel)
|
||||||
|
contentView.addSubview(categoryLabel)
|
||||||
|
contentView.addSubview(desLabel)
|
||||||
|
contentView.addSubview(playImageView)
|
||||||
|
playImageView.addSubview(playLabel)
|
||||||
|
|
||||||
|
bgImageView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(10)
|
||||||
|
make.top.equalToSuperview().offset(10)
|
||||||
|
make.width.equalTo(126)
|
||||||
|
make.height.equalTo(168)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(coverImageView.snp.right).offset(11)
|
||||||
|
make.top.equalToSuperview().offset(27)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(titleLabel)
|
||||||
|
make.top.equalTo(titleLabel.snp.bottom).offset(12)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||||
|
}
|
||||||
|
|
||||||
|
desLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(titleLabel)
|
||||||
|
make.top.equalTo(titleLabel.snp.bottom).offset(43)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||||
|
}
|
||||||
|
|
||||||
|
playImageView.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(coverImageView.snp.right).offset(11)
|
||||||
|
make.bottom.equalTo(coverImageView).offset(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
playLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(45)
|
||||||
|
make.top.equalToSuperview().offset(9)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
75
SynthReel/Class/Home/V/SRHomeBingeWorthyView.swift
Normal file
75
SynthReel/Class/Home/V/SRHomeBingeWorthyView.swift
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// SRHomeBingeWorthyView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeBingeWorthyView: SRHomeModuleView {
|
||||||
|
|
||||||
|
var dataArr: [SRShortModel]? {
|
||||||
|
didSet {
|
||||||
|
collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.scrollDirection = .horizontal
|
||||||
|
layout.minimumLineSpacing = 12
|
||||||
|
layout.itemSize = .init(width: 320, height: 192)
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectionView: SRCollectionView = {
|
||||||
|
let collectionView = SRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.showsHorizontalScrollIndicator = false
|
||||||
|
collectionView.contentInset = .init(top: 0, left: 15, bottom: 0, right: 15)
|
||||||
|
collectionView.register(SRHomeBingeWorthyCell.self, forCellWithReuseIdentifier: "cell")
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
self.titleLabel.text = "Binge-Worthy".localized
|
||||||
|
|
||||||
|
addSubview(collectionView)
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.left.right.bottom.equalToSuperview()
|
||||||
|
make.top.equalTo(button.snp.bottom).offset(6)
|
||||||
|
make.height.equalTo(collectionViewLayout.itemSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UICollectionViewDataSource UICollectionViewDelegate
|
||||||
|
extension SRHomeBingeWorthyView: UICollectionViewDataSource, UICollectionViewDelegate {
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SRHomeBingeWorthyCell
|
||||||
|
cell.model = self.dataArr?[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return self.dataArr?.count ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
self.didSelectedShort?(self.dataArr?[indexPath.row])
|
||||||
|
}
|
||||||
|
}
|
||||||
28
SynthReel/Class/Home/V/SRHomeChildCell.swift
Normal file
28
SynthReel/Class/Home/V/SRHomeChildCell.swift
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//
|
||||||
|
// SRHomeChildCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/14.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRHomeChildCell: UICollectionViewCell {
|
||||||
|
|
||||||
|
|
||||||
|
var model: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sr_setImage(model?.image_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBOutlet weak var coverImageView: SRImageView!
|
||||||
|
|
||||||
|
override func awakeFromNib() {
|
||||||
|
super.awakeFromNib()
|
||||||
|
coverImageView.layer.cornerRadius = 2
|
||||||
|
coverImageView.layer.masksToBounds = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
39
SynthReel/Class/Home/V/SRHomeChildCell.xib
Normal file
39
SynthReel/Class/Home/V/SRHomeChildCell.xib
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="24128" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24063"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="SRHomeChildCell" customModule="SynthReel" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="246" height="347"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="246" height="347"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Yvo-SQ-qpb" customClass="SRImageView" customModule="SynthReel" customModuleProvider="target">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="246" height="347"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
</view>
|
||||||
|
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="Yvo-SQ-qpb" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="1iZ-0m-azA"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="Yvo-SQ-qpb" secondAttribute="trailing" id="alg-1k-1ZY"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="Yvo-SQ-qpb" secondAttribute="bottom" id="fPg-nk-5hd"/>
|
||||||
|
<constraint firstItem="Yvo-SQ-qpb" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="wUJ-cX-k96"/>
|
||||||
|
</constraints>
|
||||||
|
<size key="customSize" width="246" height="347"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="coverImageView" destination="Yvo-SQ-qpb" id="DhO-6m-P2P"/>
|
||||||
|
</connections>
|
||||||
|
<point key="canvasLocation" x="96.18320610687023" y="179.22535211267606"/>
|
||||||
|
</collectionViewCell>
|
||||||
|
</objects>
|
||||||
|
</document>
|
||||||
156
SynthReel/Class/Home/V/SRHomeHeaderView.swift
Normal file
156
SynthReel/Class/Home/V/SRHomeHeaderView.swift
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
//
|
||||||
|
// SRHomeHeaderView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/15.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
import YYCategories
|
||||||
|
|
||||||
|
class SRHomeHeaderView: UIView {
|
||||||
|
|
||||||
|
var contentHeight: CGFloat {
|
||||||
|
return scrollView.contentSize.height + 1 + 20
|
||||||
|
}
|
||||||
|
|
||||||
|
var heightDidChange: (() -> Void)?
|
||||||
|
|
||||||
|
weak var viewModel: SRHomeViewModel?
|
||||||
|
|
||||||
|
lazy var scrollView: SRScrollView = {
|
||||||
|
let scrollView = SRScrollView()
|
||||||
|
scrollView.isScrollEnabled = false
|
||||||
|
scrollView.addObserver(self, forKeyPath: "contentSize", context: nil)
|
||||||
|
return scrollView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var stackView: UIStackView = {
|
||||||
|
let view = UIStackView()
|
||||||
|
view.axis = .vertical
|
||||||
|
view.spacing = 20
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var bannerView: SRHomeBannerView = {
|
||||||
|
let view = SRHomeBannerView()
|
||||||
|
view.didSelectedShort = { [weak self] model in
|
||||||
|
self?.pushShortDetail(model)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var youLikeView: SRHomeYouLikeView = {
|
||||||
|
let view = SRHomeYouLikeView()
|
||||||
|
view.didSelectedShort = { [weak self] model in
|
||||||
|
self?.pushShortDetail(model)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var topChartsView: SRHomeTopChartsView = {
|
||||||
|
let view = SRHomeTopChartsView()
|
||||||
|
view.didSelectedShort = { [weak self] model in
|
||||||
|
self?.pushShortDetail(model)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var bingeWorthyView: SRHomeBingeWorthyView = {
|
||||||
|
let view = SRHomeBingeWorthyView()
|
||||||
|
view.didSelectedShort = { [weak self] model in
|
||||||
|
self?.pushShortDetail(model)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var viralHitsView: SRHomeViralHitsView = {
|
||||||
|
let view = SRHomeViralHitsView()
|
||||||
|
view.didSelectedShort = { [weak self] model in
|
||||||
|
self?.pushShortDetail(model)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var premiereNowView: SRHomePremiereNowView = {
|
||||||
|
let view = SRHomePremiereNowView()
|
||||||
|
view.didSelectedShort = { [weak self] model in
|
||||||
|
self?.pushShortDetail(model)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
addSubview(scrollView)
|
||||||
|
scrollView.addSubview(stackView)
|
||||||
|
|
||||||
|
scrollView.snp.makeConstraints { make in
|
||||||
|
make.left.right.top.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview().offset(-20)
|
||||||
|
}
|
||||||
|
|
||||||
|
stackView.snp.makeConstraints { make in
|
||||||
|
make.left.centerX.top.bottom.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
if keyPath == "contentSize" {
|
||||||
|
self.heightDidChange?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func reloadData() {
|
||||||
|
stackView.sr_removeAllArrangedSubview()
|
||||||
|
|
||||||
|
var popularArr: [SRShortModel]? = nil
|
||||||
|
var updatesArr: [SRShortModel]? = nil
|
||||||
|
|
||||||
|
self.viewModel?.moduleArr.forEach {
|
||||||
|
if $0.module_key == .banner {
|
||||||
|
bannerView.dataArr = $0.list
|
||||||
|
stackView.addArrangedSubview(bannerView)
|
||||||
|
} else if $0.module_key == .detailsRecommand {
|
||||||
|
youLikeView.dataArr = $0.list
|
||||||
|
stackView.addArrangedSubview(youLikeView)
|
||||||
|
} else if $0.module_key == .popular {
|
||||||
|
popularArr = $0.list
|
||||||
|
stackView.addArrangedSubview(topChartsView)
|
||||||
|
} else if $0.module_key == .updates {
|
||||||
|
updatesArr = $0.list
|
||||||
|
stackView.addArrangedSubview(topChartsView)
|
||||||
|
} else if $0.module_key == .bingeWorthy {
|
||||||
|
bingeWorthyView.dataArr = $0.list
|
||||||
|
stackView.addArrangedSubview(bingeWorthyView)
|
||||||
|
} else if $0.module_key == .viralHits {
|
||||||
|
viralHitsView.dataArr = $0.list
|
||||||
|
stackView.addArrangedSubview(viralHitsView)
|
||||||
|
} else if $0.module_key == .premiereNow {
|
||||||
|
premiereNowView.dataArr = $0.list
|
||||||
|
stackView.addArrangedSubview(premiereNowView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
self.topChartsView.popularArr = popularArr
|
||||||
|
self.topChartsView.updatesArr = updatesArr
|
||||||
|
self.topChartsView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
func pushShortDetail(_ model: SRShortModel?) {
|
||||||
|
let vc = SRDetailPlayerViewController()
|
||||||
|
vc.shortId = model?.short_play_id
|
||||||
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
61
SynthReel/Class/Home/V/SRHomeHotView.swift
Normal file
61
SynthReel/Class/Home/V/SRHomeHotView.swift
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// SRHomeHotView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeHotView: UIView {
|
||||||
|
|
||||||
|
var count: Int = 0 {
|
||||||
|
didSet {
|
||||||
|
|
||||||
|
if count > 1000 {
|
||||||
|
countLabel.text = NSNumber(value: CGFloat(count) / 1000).toString(maximumFractionDigits: 1) + "k"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
countLabel.text = "\(count)"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var iconImageView = UIImageView(image: UIImage(named: "hot_icon_01"))
|
||||||
|
|
||||||
|
lazy var countLabel: SRLabel = {
|
||||||
|
let label = SRLabel()
|
||||||
|
label.font = .font(ofSize: 10, weight: .regular)
|
||||||
|
label.textColors = [UIColor._4_CFFD_4.cgColor, UIColor._51_D_4_FF.cgColor]
|
||||||
|
label.textStartPoint = .init(x: 0.5, y: 0)
|
||||||
|
label.textEndPoint = .init(x: 0.5, y: 1)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
addSubview(iconImageView)
|
||||||
|
addSubview(countLabel)
|
||||||
|
|
||||||
|
iconImageView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview()
|
||||||
|
make.top.equalToSuperview().offset(1)
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
countLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.right.equalToSuperview()
|
||||||
|
make.left.equalTo(iconImageView.snp.right).offset(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
51
SynthReel/Class/Home/V/SRHomeMenuCell.swift
Normal file
51
SynthReel/Class/Home/V/SRHomeMenuCell.swift
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// SRHomeMenuCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/14.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import JXSegmentedView
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeMenuCell: JXSegmentedTitleGradientCell/*JXSegmentedTitleCell*/ {
|
||||||
|
|
||||||
|
|
||||||
|
lazy var bgImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView()
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
// lazy var gTitleLabel: SRLabel = {
|
||||||
|
// let
|
||||||
|
// }()
|
||||||
|
|
||||||
|
override func commonInit() {
|
||||||
|
super.commonInit()
|
||||||
|
// self.titleLabel.removeFromSuperview()
|
||||||
|
// self.maskTitleLabel.removeFromSuperview()
|
||||||
|
|
||||||
|
self.contentView.addSubview(bgImageView)
|
||||||
|
|
||||||
|
bgImageView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.contentView.sendSubviewToBack(bgImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
override func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
|
||||||
|
super.reloadData(itemModel: itemModel, selectedType: selectedType)
|
||||||
|
|
||||||
|
if itemModel.isSelected {
|
||||||
|
bgImageView.image = UIImage(named: "home_menu_bg_image_selected")
|
||||||
|
} else {
|
||||||
|
bgImageView.image = UIImage(named: "home_menu_bg_image")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
65
SynthReel/Class/Home/V/SRHomeModuleView.swift
Normal file
65
SynthReel/Class/Home/V/SRHomeModuleView.swift
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// SRHomeModuleView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/15.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeModuleView: UIView {
|
||||||
|
|
||||||
|
var didSelectedShort: ((_ model: SRShortModel?) -> Void)?
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 18, weight: .bold)
|
||||||
|
label.textColor = .white
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var indicatorImageView = UIImageView(image: UIImage(named: "arrow_right_icon_01"))
|
||||||
|
|
||||||
|
lazy var button: UIControl = {
|
||||||
|
let button = UIControl(frame: .zero, primaryAction: UIAction(handler: { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
}))
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
|
||||||
|
addSubview(button)
|
||||||
|
button.addSubview(titleLabel)
|
||||||
|
button.addSubview(indicatorImageView)
|
||||||
|
|
||||||
|
button.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
make.height.equalTo(40)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.left.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
indicatorImageView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.right.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
198
SynthReel/Class/Home/V/SRHomePremiereNowView.swift
Normal file
198
SynthReel/Class/Home/V/SRHomePremiereNowView.swift
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
//
|
||||||
|
// SRHomePremiereNowView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomePremiereNowView: SRHomeModuleView {
|
||||||
|
|
||||||
|
|
||||||
|
var dataArr: [SRShortModel]? {
|
||||||
|
didSet {
|
||||||
|
let count = dataArr?.count ?? 0
|
||||||
|
|
||||||
|
self.oneModel = dataArr?.first
|
||||||
|
|
||||||
|
if count >= 2 {
|
||||||
|
self.twoModel = dataArr?[1]
|
||||||
|
} else {
|
||||||
|
self.twoModel = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if count >= 3 {
|
||||||
|
self.threeModel = dataArr?[2]
|
||||||
|
} else {
|
||||||
|
self.threeModel = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var oneModel: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
oneCoverImageView.sr_setImage(oneModel?.image_url)
|
||||||
|
|
||||||
|
oneTitleLabel.text = oneModel?.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var twoModel: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
if let model = twoModel {
|
||||||
|
twoCoverImageView.sr_setImage(model.image_url)
|
||||||
|
twoCoverImageView.isHidden = false
|
||||||
|
} else {
|
||||||
|
twoCoverImageView.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var threeModel: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
if let model = threeModel {
|
||||||
|
threeCoverImageView.sr_setImage(model.image_url)
|
||||||
|
threeCoverImageView.isHidden = false
|
||||||
|
} else {
|
||||||
|
threeCoverImageView.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lazy var bgImageView = UIImageView(image: UIImage(named: "premiere_now_bg_image_01"))
|
||||||
|
|
||||||
|
lazy var oneBgImageView = UIImageView(image: UIImage(named: "premiere_now_bg_image_02"))
|
||||||
|
|
||||||
|
lazy var oneCoverImageView: SRImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
imageView.isUserInteractionEnabled = true
|
||||||
|
let tap = UITapGestureRecognizer { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.didSelectedShort?(self.oneModel)
|
||||||
|
}
|
||||||
|
imageView.addGestureRecognizer(tap)
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var oneTitleBgView = UIImageView(image: UIImage(named: "premiere_now_title_bg"))
|
||||||
|
lazy var oneTitleLabel: UILabel = {
|
||||||
|
let label = SRLabel()
|
||||||
|
label.font = .font(ofSize: UIScreen.getRatioWidth(size: 12), weight: .medium)
|
||||||
|
label.numberOfLines = 2
|
||||||
|
label.textAlignment = .center
|
||||||
|
label.textColors = [UIColor.srGreen.cgColor, UIColor.srBlue.cgColor]
|
||||||
|
label.textStartPoint = .init(x: 0.5, y: 0)
|
||||||
|
label.textEndPoint = .init(x: 0.5, y: 1)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var twoCoverImageView: SRImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
imageView.isUserInteractionEnabled = true
|
||||||
|
let tap = UITapGestureRecognizer { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.didSelectedShort?(self.twoModel)
|
||||||
|
}
|
||||||
|
imageView.addGestureRecognizer(tap)
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var threeCoverImageView: SRImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
imageView.isUserInteractionEnabled = true
|
||||||
|
let tap = UITapGestureRecognizer { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.didSelectedShort?(self.threeModel)
|
||||||
|
}
|
||||||
|
imageView.addGestureRecognizer(tap)
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
self.titleLabel.text = "Premiere Now".localized
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
oneCoverImageView.applyHexagonMask(2)
|
||||||
|
twoCoverImageView.applyHexagonMask(2)
|
||||||
|
threeCoverImageView.applyHexagonMask(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHomePremiereNowView {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
oneBgImageView.isUserInteractionEnabled = true
|
||||||
|
|
||||||
|
addSubview(bgImageView)
|
||||||
|
addSubview(oneBgImageView)
|
||||||
|
oneBgImageView.addSubview(oneCoverImageView)
|
||||||
|
oneCoverImageView.addSubview(oneTitleBgView)
|
||||||
|
oneTitleBgView.addSubview(oneTitleLabel)
|
||||||
|
addSubview(twoCoverImageView)
|
||||||
|
addSubview(threeCoverImageView)
|
||||||
|
|
||||||
|
bgImageView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.centerY.equalTo(oneBgImageView)
|
||||||
|
make.height.equalTo(UIScreen.getRatioWidth(size: 155))
|
||||||
|
}
|
||||||
|
|
||||||
|
oneBgImageView.snp.makeConstraints { make in
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
make.top.equalTo(button.snp.bottom).offset(8)
|
||||||
|
make.width.equalTo(UIScreen.getRatioWidth(size: 200))
|
||||||
|
make.height.equalTo(UIScreen.getRatioWidth(size: 175))
|
||||||
|
}
|
||||||
|
|
||||||
|
oneCoverImageView.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
make.width.equalTo(UIScreen.getRatioWidth(size: 190))
|
||||||
|
make.height.equalTo(UIScreen.getRatioWidth(size: 166))
|
||||||
|
}
|
||||||
|
|
||||||
|
oneTitleBgView.snp.makeConstraints { make in
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
make.left.equalToSuperview().offset(UIScreen.getRatioWidth(size: 24.5))
|
||||||
|
make.right.equalToSuperview().offset(-UIScreen.getRatioWidth(size: 27))
|
||||||
|
make.height.equalTo(UIScreen.getRatioWidth(size: 41))
|
||||||
|
}
|
||||||
|
|
||||||
|
oneTitleLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalTo(oneTitleBgView.snp.top).offset(UIScreen.getRatioWidth(size: 23))
|
||||||
|
make.centerX.equalToSuperview().offset(UIScreen.getRatioWidth(size: 1.5))
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-UIScreen.getRatioWidth(size: 15))
|
||||||
|
}
|
||||||
|
|
||||||
|
twoCoverImageView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalTo(oneBgImageView)
|
||||||
|
make.left.equalToSuperview().offset(UIScreen.getRatioWidth(size: 20))
|
||||||
|
make.width.equalTo(UIScreen.getRatioWidth(size: 62))
|
||||||
|
make.height.equalTo(UIScreen.getRatioWidth(size: 54))
|
||||||
|
}
|
||||||
|
|
||||||
|
threeCoverImageView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalTo(oneBgImageView)
|
||||||
|
make.right.equalToSuperview().offset(-UIScreen.getRatioWidth(size: 20))
|
||||||
|
make.width.height.equalTo(twoCoverImageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
196
SynthReel/Class/Home/V/SRHomeTopChartsContentView.swift
Normal file
196
SynthReel/Class/Home/V/SRHomeTopChartsContentView.swift
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
//
|
||||||
|
// SRHomeTopChartsContentView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
|
||||||
|
extension SRHomeTopChartsView {
|
||||||
|
|
||||||
|
class ContentView: UIView {
|
||||||
|
|
||||||
|
var didSelectedShort: ((_ model: SRShortModel?) -> Void)?
|
||||||
|
|
||||||
|
var dataArr: [SRShortModel]? {
|
||||||
|
didSet {
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var bgImageView = UIImageView()
|
||||||
|
|
||||||
|
lazy var trophyImageView = UIImageView(image: UIImage(named: "trophy_icon_01"))
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 15, weight: .bold)
|
||||||
|
label.textColor = .white
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.itemSize = .init(width: 198, height: 53)
|
||||||
|
layout.minimumLineSpacing = 9
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectionView: SRCollectionView = {
|
||||||
|
let collectionView = SRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.isScrollEnabled = false
|
||||||
|
collectionView.register(ContentCell.self, forCellWithReuseIdentifier: "cell")
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
bgImageView.isUserInteractionEnabled = true
|
||||||
|
|
||||||
|
addSubview(bgImageView)
|
||||||
|
addSubview(trophyImageView)
|
||||||
|
bgImageView.addSubview(titleLabel)
|
||||||
|
bgImageView.addSubview(collectionView)
|
||||||
|
|
||||||
|
bgImageView.snp.makeConstraints { make in
|
||||||
|
make.left.right.bottom.equalToSuperview()
|
||||||
|
make.top.equalToSuperview().offset(16)
|
||||||
|
make.width.equalTo(198)
|
||||||
|
}
|
||||||
|
|
||||||
|
trophyImageView.snp.makeConstraints { make in
|
||||||
|
make.top.right.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(10)
|
||||||
|
make.top.equalToSuperview().offset(13)
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview()
|
||||||
|
make.right.equalToSuperview()
|
||||||
|
make.top.equalToSuperview().offset(46)
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UICollectionViewDelegate UICollectionViewDataSource
|
||||||
|
extension SRHomeTopChartsView.ContentView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return min(3, self.dataArr?.count ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SRHomeTopChartsView.ContentCell
|
||||||
|
cell.row = indexPath.row
|
||||||
|
cell.model = self.dataArr?[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
self.didSelectedShort?(self.dataArr?[indexPath.row])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension SRHomeTopChartsView {
|
||||||
|
class ContentCell: UICollectionViewCell {
|
||||||
|
|
||||||
|
var model: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sr_setImage(model?.image_url)
|
||||||
|
titleLabel.text = model?.name
|
||||||
|
hotView.count = model?.watch_total ?? 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var row: Int = 0 {
|
||||||
|
didSet {
|
||||||
|
numLabel.text = "\(row + 1)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var numLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 12, weight: .bold)
|
||||||
|
label.textColor = .white
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var coverImageView: SRImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
imageView.layer.cornerRadius = 2
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 12, weight: .medium)
|
||||||
|
label.textColor = .white
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var hotView: SRHomeHotView = {
|
||||||
|
let view = SRHomeHotView()
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
contentView.addSubview(numLabel)
|
||||||
|
contentView.addSubview(coverImageView)
|
||||||
|
contentView.addSubview(titleLabel)
|
||||||
|
contentView.addSubview(hotView)
|
||||||
|
|
||||||
|
numLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.centerX.equalTo(self.contentView.snp.left).offset(14)
|
||||||
|
}
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.top.bottom.equalToSuperview()
|
||||||
|
make.left.equalToSuperview().offset(24)
|
||||||
|
make.width.equalTo(40)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.top.equalToSuperview().offset(10)
|
||||||
|
make.left.equalTo(coverImageView.snp.right).offset(6)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-9)
|
||||||
|
}
|
||||||
|
|
||||||
|
hotView.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(coverImageView.snp.right).offset(6)
|
||||||
|
make.bottom.equalToSuperview().offset(-7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
106
SynthReel/Class/Home/V/SRHomeTopChartsView.swift
Normal file
106
SynthReel/Class/Home/V/SRHomeTopChartsView.swift
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
//
|
||||||
|
// SRHomeTopChartsView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeTopChartsView: SRHomeModuleView {
|
||||||
|
|
||||||
|
var popularArr: [SRShortModel]? {
|
||||||
|
didSet {
|
||||||
|
popularView.dataArr = popularArr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatesArr: [SRShortModel]? {
|
||||||
|
didSet {
|
||||||
|
updatesView.dataArr = updatesArr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lazy var scrollView: SRScrollView = {
|
||||||
|
let scrollView = SRScrollView()
|
||||||
|
scrollView.showsHorizontalScrollIndicator = false
|
||||||
|
return scrollView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var stackView: UIStackView = {
|
||||||
|
let view = UIStackView()
|
||||||
|
view.axis = .horizontal
|
||||||
|
view.spacing = 16
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var popularView: ContentView = {
|
||||||
|
let view = ContentView()
|
||||||
|
view.titleLabel.text = "Popular".localized
|
||||||
|
view.bgImageView.image = UIImage(named: "popular_bg_image")
|
||||||
|
view.didSelectedShort = { [weak self] model in
|
||||||
|
self?.didSelectedShort?(model)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var updatesView: ContentView = {
|
||||||
|
let view = ContentView()
|
||||||
|
view.titleLabel.text = "Updates".localized
|
||||||
|
view.bgImageView.image = UIImage(named: "updates_bg_image")
|
||||||
|
view.didSelectedShort = { [weak self] model in
|
||||||
|
self?.didSelectedShort?(model)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
self.titleLabel.text = "Top Charts".localized
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func reloadData() {
|
||||||
|
stackView.sr_removeAllArrangedSubview()
|
||||||
|
|
||||||
|
if let arr = self.popularArr, arr.count > 0 {
|
||||||
|
stackView.addArrangedSubview(popularView)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let arr = self.updatesArr, arr.count > 0 {
|
||||||
|
stackView.addArrangedSubview(updatesView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHomeTopChartsView {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
addSubview(scrollView)
|
||||||
|
scrollView.addSubview(stackView)
|
||||||
|
|
||||||
|
scrollView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.top.equalTo(self.button.snp.bottom)
|
||||||
|
make.height.equalTo(238 + 16)
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
stackView.snp.makeConstraints { make in
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
make.height.equalTo(238 + 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
SynthReel/Class/Home/V/SRHomeViralHitsCell.swift
Normal file
86
SynthReel/Class/Home/V/SRHomeViralHitsCell.swift
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
//
|
||||||
|
// SRHomeViralHitsCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeViralHitsCell: UICollectionViewCell {
|
||||||
|
|
||||||
|
var model: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sr_setImage(model?.image_url)
|
||||||
|
titleLabel.text = model?.name
|
||||||
|
hotView.count = model?.watch_total ?? 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var bgImageView = UIImageView(image: UIImage(named: "home_viral_hits_cell_image"))
|
||||||
|
|
||||||
|
lazy var coverImageView: SRImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
imageView.layer.cornerRadius = 2
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 12, weight: .medium)
|
||||||
|
label.textColor = .white
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var hotView: SRHomeHotView = {
|
||||||
|
let view = SRHomeHotView()
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHomeViralHitsCell {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
contentView.addSubview(bgImageView)
|
||||||
|
contentView.addSubview(coverImageView)
|
||||||
|
coverImageView.addSubview(hotView)
|
||||||
|
contentView.addSubview(titleLabel)
|
||||||
|
|
||||||
|
bgImageView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.left.equalToSuperview().offset(13)
|
||||||
|
make.top.equalToSuperview().offset(21)
|
||||||
|
make.height.equalTo(180)
|
||||||
|
}
|
||||||
|
|
||||||
|
hotView.snp.makeConstraints { make in
|
||||||
|
make.right.equalToSuperview().offset(-5)
|
||||||
|
make.top.equalToSuperview().offset(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(13)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-13)
|
||||||
|
make.top.equalTo(coverImageView.snp.bottom).offset(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
74
SynthReel/Class/Home/V/SRHomeViralHitsView.swift
Normal file
74
SynthReel/Class/Home/V/SRHomeViralHitsView.swift
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
//
|
||||||
|
// SRHomeViralHitsView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeViralHitsView: SRHomeModuleView {
|
||||||
|
|
||||||
|
var dataArr: [SRShortModel]? {
|
||||||
|
didSet {
|
||||||
|
collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.scrollDirection = .horizontal
|
||||||
|
layout.minimumLineSpacing = 12
|
||||||
|
layout.itemSize = .init(width: 161, height: 237)
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectionView: SRCollectionView = {
|
||||||
|
let collectionView = SRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.showsHorizontalScrollIndicator = false
|
||||||
|
collectionView.contentInset = .init(top: 0, left: 15, bottom: 0, right: 15)
|
||||||
|
collectionView.register(SRHomeViralHitsCell.self, forCellWithReuseIdentifier: "cell")
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
self.titleLabel.text = "Viral Hits".localized
|
||||||
|
|
||||||
|
addSubview(collectionView)
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.left.right.bottom.equalToSuperview()
|
||||||
|
make.top.equalTo(button.snp.bottom).offset(6)
|
||||||
|
make.height.equalTo(collectionViewLayout.itemSize.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//MARK: UICollectionViewDelegate UICollectionViewDataSource
|
||||||
|
extension SRHomeViralHitsView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SRHomeViralHitsCell
|
||||||
|
cell.model = dataArr?[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return self.dataArr?.count ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
self.didSelectedShort?(self.dataArr?[indexPath.row])
|
||||||
|
}
|
||||||
|
}
|
||||||
120
SynthReel/Class/Home/V/SRHomeYouLikeCell.swift
Normal file
120
SynthReel/Class/Home/V/SRHomeYouLikeCell.swift
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
//
|
||||||
|
// SRHomeYouLikeCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/15.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeYouLikeCell: UICollectionViewCell {
|
||||||
|
|
||||||
|
var model: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sr_setImage(model?.image_url)
|
||||||
|
|
||||||
|
titleLabel.text = model?.name
|
||||||
|
desLabel.text = model?.sr_description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var bgView: UIView = UIImageView(image: UIImage(named: "cell_bg_image_01"))
|
||||||
|
|
||||||
|
lazy var coverBgView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.layer.cornerRadius = 34
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
view.layer.borderWidth = 0.7
|
||||||
|
view.layer.borderColor = UIColor._4_CFFD_4.withAlphaComponent(0.25).cgColor
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var coverBorderView: SRGradientView = {
|
||||||
|
let view = SRGradientView()
|
||||||
|
view.layer.cornerRadius = 31
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
view.colors = [UIColor._4_CFFD_4.cgColor, UIColor._51_D_4_FF.cgColor]
|
||||||
|
view.startPoint = .init(x: 0, y: 0)
|
||||||
|
view.endPoint = .init(x: 1, y: 1)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var coverImageView: SRImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
imageView.layer.cornerRadius = 30.25
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 14, weight: .medium)
|
||||||
|
label.textColor = .white
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var desLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 11, weight: .regular)
|
||||||
|
label.textColor = .A_6_A_6_A_6
|
||||||
|
label.numberOfLines = 2
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHomeYouLikeCell {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
contentView.addSubview(bgView)
|
||||||
|
bgView.addSubview(coverBgView)
|
||||||
|
coverBgView.addSubview(coverBorderView)
|
||||||
|
coverBgView.addSubview(coverImageView)
|
||||||
|
bgView.addSubview(titleLabel)
|
||||||
|
bgView.addSubview(desLabel)
|
||||||
|
|
||||||
|
bgView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
coverBgView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.left.equalToSuperview().offset(8)
|
||||||
|
make.width.height.equalTo(68)
|
||||||
|
}
|
||||||
|
|
||||||
|
coverBorderView.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
make.width.height.equalTo(62)
|
||||||
|
}
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
make.width.height.equalTo(60.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(coverBgView.snp.right).offset(9)
|
||||||
|
make.top.equalToSuperview().offset(17)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-12)
|
||||||
|
}
|
||||||
|
|
||||||
|
desLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(titleLabel)
|
||||||
|
make.top.equalTo(titleLabel.snp.bottom).offset(11)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
81
SynthReel/Class/Home/V/SRHomeYouLikeView.swift
Normal file
81
SynthReel/Class/Home/V/SRHomeYouLikeView.swift
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
//
|
||||||
|
// SRHomeYouLikeView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/15.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeYouLikeView: UIView {
|
||||||
|
|
||||||
|
var dataArr: [SRShortModel]? {
|
||||||
|
didSet {
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var didSelectedShort: ((_ model: SRShortModel?) -> Void)?
|
||||||
|
|
||||||
|
lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.scrollDirection = .horizontal
|
||||||
|
layout.minimumLineSpacing = 14
|
||||||
|
layout.itemSize = .init(width: 220, height: 82)
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectionView: SRCollectionView = {
|
||||||
|
let collectionView = SRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.showsHorizontalScrollIndicator = false
|
||||||
|
collectionView.contentInset = .init(top: 0, left: 15, bottom: 0, right: 15)
|
||||||
|
collectionView.register(SRHomeYouLikeCell.self, forCellWithReuseIdentifier: "cell")
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHomeYouLikeView {
|
||||||
|
|
||||||
|
func sr_setupUI() {
|
||||||
|
|
||||||
|
addSubview(collectionView)
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.left.right.top.bottom.equalToSuperview()
|
||||||
|
make.height.equalTo(82)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UICollectionViewDelegate UICollectionViewDataSource
|
||||||
|
extension SRHomeYouLikeView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SRHomeYouLikeCell
|
||||||
|
cell.model = self.dataArr?[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return self.dataArr?.count ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
self.didSelectedShort?(self.dataArr?[indexPath.row])
|
||||||
|
}
|
||||||
|
}
|
||||||
114
SynthReel/Class/Home/V/SRHotSearchCell.swift
Normal file
114
SynthReel/Class/Home/V/SRHotSearchCell.swift
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
//
|
||||||
|
// SRHotSearchCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHotSearchCell: SRTableViewCell {
|
||||||
|
|
||||||
|
var model: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sr_setImage(model?.image_url)
|
||||||
|
titleLabel.text = model?.name
|
||||||
|
hotView.count = model?.watch_total ?? 0
|
||||||
|
desLabel.text = model?.sr_description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var row: Int = 0 {
|
||||||
|
didSet {
|
||||||
|
numLabel.text = "\(row + 1)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var numLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 12, weight: .bold)
|
||||||
|
label.textColor = .white
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var coverImageView: SRImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
imageView.layer.cornerRadius = 2
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 14, weight: .medium)
|
||||||
|
label.textColor = .white
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var hotView: SRHomeHotView = {
|
||||||
|
let view = SRHomeHotView()
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var desLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 11, weight: .regular)
|
||||||
|
label.textColor = .A_6_A_6_A_6
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHotSearchCell {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
contentView.addSubview(numLabel)
|
||||||
|
contentView.addSubview(coverImageView)
|
||||||
|
contentView.addSubview(titleLabel)
|
||||||
|
contentView.addSubview(hotView)
|
||||||
|
contentView.addSubview(desLabel)
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(24)
|
||||||
|
make.width.equalTo(40)
|
||||||
|
make.height.equalTo(53)
|
||||||
|
make.top.equalToSuperview().offset(8)
|
||||||
|
make.bottom.equalToSuperview().offset(-8)
|
||||||
|
}
|
||||||
|
|
||||||
|
numLabel.snp.makeConstraints { make in
|
||||||
|
make.centerX.equalTo(self.contentView.snp.left).offset(14)
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(coverImageView.snp.right).offset(6)
|
||||||
|
make.top.equalTo(coverImageView).offset(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
hotView.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(titleLabel.snp.right).offset(15)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-30)
|
||||||
|
make.centerY.equalTo(titleLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
desLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(titleLabel)
|
||||||
|
make.bottom.equalTo(coverImageView).offset(-6)
|
||||||
|
make.right.equalToSuperview().offset(-27)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
115
SynthReel/Class/Home/V/SRHotSearchView.swift
Normal file
115
SynthReel/Class/Home/V/SRHotSearchView.swift
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
//
|
||||||
|
// SRHotSearchView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
import YYCategories
|
||||||
|
|
||||||
|
class SRHotSearchView: UIView {
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
return .init(width: UIScreen.width, height: UIScreen.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataArr: [SRShortModel] = [] {
|
||||||
|
didSet {
|
||||||
|
self.tableView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var bgImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "search_hot_bg_image"))
|
||||||
|
imageView.isUserInteractionEnabled = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = SRLabel()
|
||||||
|
label.font = .font(ofSize: 15, weight: .semibold)
|
||||||
|
label.textColors = [UIColor._7_AF_4_E_0.cgColor, UIColor.white.cgColor, UIColor._7_AF_4_E_0.cgColor]
|
||||||
|
label.textStartPoint = .init(x: 0, y: 0.5)
|
||||||
|
label.textEndPoint = .init(x: 1, y: 0.5)
|
||||||
|
label.text = "Premium Picks".localized
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var tableView: SRTableView = {
|
||||||
|
let tableView = SRTableView(frame: .zero, style: .plain)
|
||||||
|
tableView.delegate = self
|
||||||
|
tableView.dataSource = self
|
||||||
|
tableView.separatorStyle = .none
|
||||||
|
tableView.showsVerticalScrollIndicator = false
|
||||||
|
tableView.keyboardDismissMode = .onDrag
|
||||||
|
tableView.contentInset = .init(top: 0, left: 0, bottom: 5, right: 0)
|
||||||
|
tableView.register(SRHotSearchCell.self, forCellReuseIdentifier: "cell")
|
||||||
|
return tableView
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHotSearchView {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
addSubview(bgImageView)
|
||||||
|
bgImageView.addSubview(titleLabel)
|
||||||
|
bgImageView.addSubview(tableView)
|
||||||
|
|
||||||
|
bgImageView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview().offset(-(UIScreen.safeBottom + 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(10)
|
||||||
|
make.top.equalToSuperview().offset(13)
|
||||||
|
}
|
||||||
|
|
||||||
|
tableView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.top.equalToSuperview().offset(38)
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UITableViewDelegate UITableViewDataSource
|
||||||
|
extension SRHotSearchView: UITableViewDelegate, UITableViewDataSource {
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SRHotSearchCell
|
||||||
|
cell.row = indexPath.row
|
||||||
|
cell.model = dataArr[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
|
return self.dataArr.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
let model = dataArr[indexPath.row]
|
||||||
|
let vc = SRDetailPlayerViewController()
|
||||||
|
vc.shortId = model.short_play_id
|
||||||
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
93
SynthReel/Class/Home/V/SRSearchHomeView.swift
Normal file
93
SynthReel/Class/Home/V/SRSearchHomeView.swift
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
//
|
||||||
|
// SRSearchHomeView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRSearchHomeView: UIView {
|
||||||
|
|
||||||
|
var didSearch: ((_ text: String) -> Void)?
|
||||||
|
|
||||||
|
weak var viewModel: SRSearchViewModel? {
|
||||||
|
didSet {
|
||||||
|
viewModel?.addObserver(self, forKeyPath: "hotDataArr", context: nil)
|
||||||
|
viewModel?.addObserver(self, forKeyPath: "recordList", context: nil)
|
||||||
|
|
||||||
|
self.hotView.dataArr = self.viewModel?.hotDataArr ?? []
|
||||||
|
self.recordView.dataArr = self.viewModel?.recordList ?? []
|
||||||
|
updateLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var stackView: UIStackView = {
|
||||||
|
let view = UIStackView()
|
||||||
|
view.spacing = 20
|
||||||
|
view.axis = .vertical
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var recordView: SRSearchRecordView = {
|
||||||
|
let view = SRSearchRecordView()
|
||||||
|
view.didSearch = { [weak self] text in
|
||||||
|
self?.didSearch?(text)
|
||||||
|
}
|
||||||
|
view.didDeleteAll = { [weak self] in
|
||||||
|
self?.viewModel?.clearSearchRecord()
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var hotView: SRHotSearchView = {
|
||||||
|
let view = SRHotSearchView()
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
viewModel?.removeObserver(self, forKeyPath: "hotDataArr")
|
||||||
|
viewModel?.removeObserver(self, forKeyPath: "recordList")
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
addSubview(stackView)
|
||||||
|
|
||||||
|
stackView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.top.equalToSuperview().offset(14)
|
||||||
|
make.bottom.lessThanOrEqualToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
if keyPath == "hotDataArr" {
|
||||||
|
self.hotView.dataArr = self.viewModel?.hotDataArr ?? []
|
||||||
|
} else if keyPath == "recordList" {
|
||||||
|
self.recordView.dataArr = self.viewModel?.recordList ?? []
|
||||||
|
}
|
||||||
|
updateLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout() {
|
||||||
|
stackView.sr_removeAllArrangedSubview()
|
||||||
|
|
||||||
|
if self.recordView.dataArr.count > 0 {
|
||||||
|
stackView.addArrangedSubview(recordView)
|
||||||
|
}
|
||||||
|
if self.hotView.dataArr.count > 0 {
|
||||||
|
stackView.addArrangedSubview(self.hotView)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
42
SynthReel/Class/Home/V/SRSearchRecordCell.swift
Normal file
42
SynthReel/Class/Home/V/SRSearchRecordCell.swift
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// SRSearchRecordCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRSearchRecordCell: UICollectionViewCell {
|
||||||
|
|
||||||
|
static let TextFont: UIFont = .font(ofSize: 12, weight: .regular)
|
||||||
|
|
||||||
|
lazy var textLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = Self.TextFont
|
||||||
|
label.textColor = .A_6_A_6_A_6
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
contentView.backgroundColor = ._010101.withAlphaComponent(0.2)
|
||||||
|
contentView.layer.cornerRadius = 14
|
||||||
|
contentView.layer.masksToBounds = true
|
||||||
|
contentView.layer.borderWidth = 1
|
||||||
|
contentView.layer.borderColor = UIColor._4_CFFD_4.withAlphaComponent(0.25).cgColor
|
||||||
|
|
||||||
|
contentView.addSubview(textLabel)
|
||||||
|
|
||||||
|
textLabel.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
135
SynthReel/Class/Home/V/SRSearchRecordView.swift
Normal file
135
SynthReel/Class/Home/V/SRSearchRecordView.swift
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
//
|
||||||
|
// SRSearchRecordView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
import collection_view_layouts
|
||||||
|
|
||||||
|
class SRSearchRecordView: UIView {
|
||||||
|
|
||||||
|
var didSearch: ((_ text: String) -> Void)?
|
||||||
|
var didDeleteAll: (() -> Void)?
|
||||||
|
|
||||||
|
var dataArr: [String] = [] {
|
||||||
|
didSet {
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 12, weight: .medium)
|
||||||
|
label.textColor = .CCCCCC
|
||||||
|
label.text = "Search History".localized
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var deleteButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.didDeleteAll?()
|
||||||
|
}))
|
||||||
|
button.setImage(UIImage(named: "delete_icon_01"), for: .normal)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var collectionViewLayout: TagsLayout = {
|
||||||
|
let layout = TagsLayout()
|
||||||
|
layout.delegate = self
|
||||||
|
layout.contentPadding = ItemsPadding(horizontal: 15, vertical: 0)
|
||||||
|
layout.cellsPadding = ItemsPadding(horizontal: 9, vertical: 10)
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var collectionView: SRCollectionView = {
|
||||||
|
let collectionView = SRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.addObserver(self, forKeyPath: "contentSize", context: nil)
|
||||||
|
collectionView.register(SRSearchRecordCell.self, forCellWithReuseIdentifier: "tagCell")
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.collectionView.removeObserver(self, forKeyPath: "contentSize")
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
if keyPath == "contentSize" {
|
||||||
|
let height = self.collectionView.contentSize.height
|
||||||
|
self.collectionView.snp.updateConstraints { make in
|
||||||
|
make.height.equalTo(height + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRSearchRecordView {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
addSubview(titleLabel)
|
||||||
|
addSubview(deleteButton)
|
||||||
|
addSubview(collectionView)
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(14)
|
||||||
|
make.centerY.equalTo(deleteButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteButton.snp.makeConstraints { make in
|
||||||
|
make.right.equalToSuperview().offset(-5)
|
||||||
|
make.top.equalToSuperview().offset(-10)
|
||||||
|
make.width.equalTo(36)
|
||||||
|
make.height.equalTo(36)
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.top.equalToSuperview().offset(24)
|
||||||
|
make.left.equalToSuperview()
|
||||||
|
make.right.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
make.height.equalTo(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UICollectionViewDelegate UICollectionViewDataSource
|
||||||
|
extension SRSearchRecordView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return dataArr.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "tagCell", for: indexPath) as! SRSearchRecordCell
|
||||||
|
cell.textLabel.text = dataArr[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
self.didSearch?(dataArr[indexPath.row])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRSearchRecordView: LayoutDelegate {
|
||||||
|
func cellSize(indexPath: IndexPath) -> CGSize {
|
||||||
|
let text = dataArr[indexPath.row]
|
||||||
|
let size = text.size(SRSearchRecordCell.TextFont, .init(width: UIScreen.width, height: 24))
|
||||||
|
return .init(width: size.width + 20, height: 28)
|
||||||
|
}
|
||||||
|
}
|
||||||
139
SynthReel/Class/Home/V/SRSearchResultCell.swift
Normal file
139
SynthReel/Class/Home/V/SRSearchResultCell.swift
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
//
|
||||||
|
// SRSearchResultCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRSearchResultCell: UICollectionViewCell {
|
||||||
|
|
||||||
|
|
||||||
|
var model: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sr_setImage(model?.image_url)
|
||||||
|
titleLabel.text = model?.name
|
||||||
|
desLabel.text = model?.sr_description
|
||||||
|
if let category = model?.categoryList?.first?.name, category.count > 0 {
|
||||||
|
categoryLabel.text = "#\(category)"
|
||||||
|
} else {
|
||||||
|
categoryLabel.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var bgImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "search_result_cell_bg_image"))
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var coverImageView: SRImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
imageView.layer.cornerRadius = 4
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 14, weight: .medium)
|
||||||
|
label.textColor = .white
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var categoryLabel: SRLabel = {
|
||||||
|
let label = SRLabel()
|
||||||
|
label.font = .font(ofSize: 11, weight: .regular)
|
||||||
|
label.textColors = [UIColor.srGreen.cgColor, UIColor.srBlue.cgColor]
|
||||||
|
label.textStartPoint = .init(x: 0.5, y: 0)
|
||||||
|
label.textEndPoint = .init(x: 0.5, y: 1)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var desLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 11, weight: .regular)
|
||||||
|
label.textColor = .A_6_A_6_A_6
|
||||||
|
label.numberOfLines = 3
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var playImageView = UIImageView(image: UIImage(named: "play_icon_01"))
|
||||||
|
|
||||||
|
lazy var playLabel: UILabel = {
|
||||||
|
let label = SRLabel()
|
||||||
|
label.font = .font(ofSize: 11, weight: .regular)
|
||||||
|
label.textColors = [UIColor.srGreen.cgColor, UIColor.srBlue.cgColor]
|
||||||
|
label.textStartPoint = .init(x: 0.5, y: 0)
|
||||||
|
label.textEndPoint = .init(x: 0.5, y: 1)
|
||||||
|
label.text = "Play"
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRSearchResultCell {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
contentView.addSubview(bgImageView)
|
||||||
|
contentView.addSubview(coverImageView)
|
||||||
|
contentView.addSubview(titleLabel)
|
||||||
|
contentView.addSubview(categoryLabel)
|
||||||
|
contentView.addSubview(desLabel)
|
||||||
|
contentView.addSubview(playImageView)
|
||||||
|
playImageView.addSubview(playLabel)
|
||||||
|
|
||||||
|
bgImageView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(10)
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.width.equalTo(126)
|
||||||
|
make.height.equalTo(168)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.top.equalTo(coverImageView).offset(17)
|
||||||
|
make.left.equalTo(coverImageView.snp.right).offset(10)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-13)
|
||||||
|
}
|
||||||
|
|
||||||
|
categoryLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(titleLabel)
|
||||||
|
make.top.equalTo(titleLabel.snp.bottom).offset(12)
|
||||||
|
}
|
||||||
|
|
||||||
|
desLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(titleLabel)
|
||||||
|
make.top.equalTo(titleLabel.snp.bottom).offset(43)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-12)
|
||||||
|
}
|
||||||
|
|
||||||
|
playImageView.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(titleLabel)
|
||||||
|
make.bottom.equalTo(coverImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
playLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(45)
|
||||||
|
make.top.equalToSuperview().offset(9)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
111
SynthReel/Class/Home/V/SRSearchResultView.swift
Normal file
111
SynthReel/Class/Home/V/SRSearchResultView.swift
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
//
|
||||||
|
// SRSearchResultView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
import YYCategories
|
||||||
|
|
||||||
|
class SRSearchResultView: UIView {
|
||||||
|
|
||||||
|
weak var viewModel: SRSearchViewModel?
|
||||||
|
|
||||||
|
lazy var dataArr: [SRShortModel] = []
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = SRLabel()
|
||||||
|
label.font = .font(ofSize: 15, weight: .semibold)
|
||||||
|
label.textColors = [UIColor._7_AF_4_E_0.cgColor, UIColor.white.cgColor, UIColor._7_AF_4_E_0.cgColor]
|
||||||
|
label.textStartPoint = .init(x: 0, y: 0.5)
|
||||||
|
label.textEndPoint = .init(x: 1, y: 0.5)
|
||||||
|
label.text = "Search Results".localized
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.itemSize = .init(width: UIScreen.width - 30, height: 192)
|
||||||
|
layout.minimumLineSpacing = 10
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectionView: SRCollectionView = {
|
||||||
|
let collectionView = SRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.keyboardDismissMode = .onDrag
|
||||||
|
collectionView.showsVerticalScrollIndicator = false
|
||||||
|
collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.safeBottom + 10, right: 0)
|
||||||
|
collectionView.register(SRSearchResultCell.self, forCellWithReuseIdentifier: "cell")
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func search(_ text: String) {
|
||||||
|
self.dataArr.removeAll()
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
|
||||||
|
Task {
|
||||||
|
if let arr = await SRHomeApi.requestSearch(text) {
|
||||||
|
self.dataArr = arr
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRSearchResultView {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
addSubview(titleLabel)
|
||||||
|
addSubview(collectionView)
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.top.equalToSuperview().offset(30)
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
make.top.equalToSuperview().offset(59)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UICollectionViewDelegate UICollectionViewDataSource
|
||||||
|
extension SRSearchResultView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SRSearchResultCell
|
||||||
|
cell.model = dataArr[indexPath.row]
|
||||||
|
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]
|
||||||
|
let vc = SRDetailPlayerViewController()
|
||||||
|
vc.shortId = model.short_play_id
|
||||||
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
125
SynthReel/Class/Home/V/SRSearchTextView.swift
Normal file
125
SynthReel/Class/Home/V/SRSearchTextView.swift
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
//
|
||||||
|
// SRSearchTextView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRSearchTextView: UIView {
|
||||||
|
|
||||||
|
var didSearch: ((_ text: String) -> Void)?
|
||||||
|
|
||||||
|
var text: String? {
|
||||||
|
get {
|
||||||
|
return textField.text
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
textField.text = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var bgImageView = UIImageView(image: UIImage(named: "search_text_bg_image"))
|
||||||
|
|
||||||
|
lazy var button: UIButton = {
|
||||||
|
var configuration = UIButton.Configuration.plain()
|
||||||
|
configuration.background.cornerRadius = 0
|
||||||
|
configuration.background.image = UIImage(named: "search_text_button")
|
||||||
|
configuration.contentInsets = .init(top: 0, leading: 10, bottom: 0, trailing: 10)
|
||||||
|
configuration.attributedTitle = AttributedString("Search".localized, attributes: AttributeContainer([
|
||||||
|
.font : UIFont.font(ofSize: 14, weight: .medium),
|
||||||
|
.foregroundColor : UIColor._010101
|
||||||
|
]))
|
||||||
|
let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.handleSearch()
|
||||||
|
}))
|
||||||
|
button.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
button.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var textField: UITextField = {
|
||||||
|
let textField = UITextField(frame: .zero)
|
||||||
|
textField.tintColor = UIColor.white.withAlphaComponent(0.5)
|
||||||
|
textField.delegate = self
|
||||||
|
textField.returnKeyType = .search
|
||||||
|
textField.font = .font(ofSize: 12, weight: .medium)
|
||||||
|
textField.textColor = .white
|
||||||
|
textField.attributedPlaceholder = NSAttributedString(string: "search_placeholder_text".localized, attributes: [
|
||||||
|
.font : UIFont.font(ofSize: 12, weight: .medium),
|
||||||
|
.foregroundColor : UIColor.white.withAlphaComponent(0.3)
|
||||||
|
])
|
||||||
|
return textField
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
override func becomeFirstResponder() -> Bool {
|
||||||
|
super.becomeFirstResponder()
|
||||||
|
return self.textField.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
override func resignFirstResponder() -> Bool {
|
||||||
|
super.resignFirstResponder()
|
||||||
|
return self.textField.resignFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleSearch() {
|
||||||
|
if let text = textField.text {
|
||||||
|
self.didSearch?(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension SRSearchTextView {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
addSubview(bgImageView)
|
||||||
|
addSubview(button)
|
||||||
|
addSubview(textField)
|
||||||
|
|
||||||
|
bgImageView.snp.makeConstraints { make in
|
||||||
|
make.top.bottom.left.equalToSuperview()
|
||||||
|
make.right.equalToSuperview().offset(-8)
|
||||||
|
make.height.equalTo(40)
|
||||||
|
}
|
||||||
|
|
||||||
|
button.snp.makeConstraints { make in
|
||||||
|
make.top.bottom.equalToSuperview()
|
||||||
|
make.right.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
textField.snp.makeConstraints { make in
|
||||||
|
make.top.bottom.equalToSuperview()
|
||||||
|
make.left.equalToSuperview().offset(14)
|
||||||
|
make.right.equalTo(button.snp.left).offset(-15)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UITextFieldDelegate
|
||||||
|
extension SRSearchTextView: UITextFieldDelegate {
|
||||||
|
|
||||||
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
handleSearch()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
143
SynthReel/Class/Home/VC/SRHomeChildViewController.swift
Normal file
143
SynthReel/Class/Home/VC/SRHomeChildViewController.swift
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
//
|
||||||
|
// SRHomeChildViewController.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/14.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRHomeChildViewController: SRViewController {
|
||||||
|
|
||||||
|
var categoryId: String?
|
||||||
|
|
||||||
|
var dataArr: [SRShortModel] = []
|
||||||
|
var page = 1
|
||||||
|
|
||||||
|
lazy var bgImageView = UIImageView(image: UIImage(named: "home_list_bg_image"))
|
||||||
|
|
||||||
|
lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let itemWidth = floor((UIScreen.width - 30 - 26 - 10) / 2)
|
||||||
|
let itemHeight = 206.0 / 155.0 * itemWidth
|
||||||
|
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.minimumInteritemSpacing = 10
|
||||||
|
layout.minimumLineSpacing = 10
|
||||||
|
layout.itemSize = .init(width: itemWidth, height: itemHeight)
|
||||||
|
layout.sectionInset = .init(top: 0, left: 13 + 15, bottom: 0, right: 13 + 15)
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectionView: SRCollectionView = {
|
||||||
|
let collectionView = SRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.showsVerticalScrollIndicator = false
|
||||||
|
collectionView.showsHorizontalScrollIndicator = false
|
||||||
|
collectionView.register(UINib(nibName: "SRHomeChildCell", bundle: nil), forCellWithReuseIdentifier: "cell")
|
||||||
|
collectionView.sr_addRefreshFooter { [weak self] in
|
||||||
|
self?.handleFooterRefresh(nil)
|
||||||
|
}
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
self.view.backgroundColor = .clear
|
||||||
|
self.backgroundImageView.isHidden = true
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
|
||||||
|
Task {
|
||||||
|
await requestDataArr(page: self.page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override func listScrollView() -> UIScrollView {
|
||||||
|
return collectionView
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
|
||||||
|
Task {
|
||||||
|
await self.requestDataArr(page: 1)
|
||||||
|
completer?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func handleFooterRefresh(_ completer: (() -> Void)?) {
|
||||||
|
Task {
|
||||||
|
await self.requestDataArr(page: self.page + 1)
|
||||||
|
self.collectionView.sr_endFooterRefreshing()
|
||||||
|
completer?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHomeChildViewController {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
view.addSubview(bgImageView)
|
||||||
|
view.addSubview(collectionView)
|
||||||
|
|
||||||
|
bgImageView.snp.makeConstraints { make in
|
||||||
|
make.top.equalToSuperview().offset(20)
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview().offset(-15)
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.top.equalTo(bgImageView).offset(30)
|
||||||
|
make.bottom.equalTo(bgImageView).offset(-27)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UICollectionViewDelegate UICollectionViewDataSource
|
||||||
|
extension SRHomeChildViewController: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SRHomeChildCell
|
||||||
|
cell.model = self.dataArr[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return self.dataArr.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
self.didScrollCallback?(scrollView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
let model = dataArr[indexPath.row]
|
||||||
|
|
||||||
|
let vc = SRDetailPlayerViewController()
|
||||||
|
vc.shortId = model.short_play_id
|
||||||
|
self.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHomeChildViewController {
|
||||||
|
|
||||||
|
private func requestDataArr(page: Int) async {
|
||||||
|
guard let id = categoryId else { return }
|
||||||
|
|
||||||
|
if let dataArr = await SRHomeApi.requestCategoryVideoData(id, page: page) {
|
||||||
|
if page == 1 {
|
||||||
|
self.dataArr.removeAll()
|
||||||
|
}
|
||||||
|
self.dataArr += dataArr
|
||||||
|
self.page = page
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
279
SynthReel/Class/Home/VC/SRHomeViewController.swift
Normal file
279
SynthReel/Class/Home/VC/SRHomeViewController.swift
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
//
|
||||||
|
// SRHomeViewController.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
import YYCategories
|
||||||
|
import JXPagingView
|
||||||
|
import JXSegmentedView
|
||||||
|
|
||||||
|
|
||||||
|
extension JXPagingListContainerView: @retroactive JXSegmentedViewListContainer { }
|
||||||
|
|
||||||
|
class SRHomeViewController: SRViewController {
|
||||||
|
|
||||||
|
var viewModel = SRHomeViewModel()
|
||||||
|
|
||||||
|
|
||||||
|
lazy var searchButton: UIButton = {
|
||||||
|
var configuration = UIButton.Configuration.plain()
|
||||||
|
configuration.image = UIImage(named: "search_icon_01")
|
||||||
|
configuration.contentInsets = .init(top: 0, leading: 15, bottom: 0, trailing: 15)
|
||||||
|
|
||||||
|
let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let vc = SRSearchViewController()
|
||||||
|
self.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}))
|
||||||
|
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var titleImageView = UIImageView(image: UIImage(named: "home_title_image"))
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: UIScreen.getRatioWidth(size: 20), weight: .bold).withBoldItalic()
|
||||||
|
label.textColor = .white
|
||||||
|
label.text = "Drama Center".localized
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var menuContentView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var moreButton: UIControl = {
|
||||||
|
let view = UIControl()
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var moreTitleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 18, weight: .bold)
|
||||||
|
label.textColor = .white
|
||||||
|
label.text = "Categories".localized
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var moreIconImageView = UIImageView(image: UIImage(named: "arrow_right_icon_01"))
|
||||||
|
|
||||||
|
lazy var menuDataSource: JXSegmentedTitleGradientDataSource = {
|
||||||
|
let dataSource = SRHomeMenuDataSource()
|
||||||
|
dataSource.titleNormalGradientColors = [UIColor.white.cgColor, UIColor._96_E_5_FF.cgColor, UIColor.white.cgColor]
|
||||||
|
dataSource.titleSelectedGradientColors = [UIColor.white.cgColor, UIColor._96_E_5_FF.cgColor, UIColor.white.cgColor]
|
||||||
|
dataSource.titleGradientStartPoint = .init(x: 0.5, y: 0)
|
||||||
|
dataSource.titleGradientEndPoint = .init(x: 0.5, y: 1)
|
||||||
|
dataSource.titleNormalFont = .font(ofSize: 13, weight: .medium)
|
||||||
|
dataSource.titleSelectedFont = .font(ofSize: 13, weight: .medium)
|
||||||
|
dataSource.itemWidth = 100
|
||||||
|
dataSource.itemWidthIncrement = 5
|
||||||
|
dataSource.titleNumberOfLines = 2
|
||||||
|
dataSource.itemSpacing = 5
|
||||||
|
return dataSource
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var menuView: JXSegmentedView = {
|
||||||
|
let view = JXSegmentedView()
|
||||||
|
view.dataSource = menuDataSource
|
||||||
|
view.contentEdgeInsetLeft = 15
|
||||||
|
view.contentEdgeInsetRight = 15
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var pageView: JXPagingView = {
|
||||||
|
let view = JXPagingView(delegate: self)
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
view.mainTableView.backgroundColor = .clear
|
||||||
|
view.listContainerView.listCellBackgroundColor = .clear
|
||||||
|
view.mainTableView.gestureDelegate = self
|
||||||
|
view.automaticallyDisplayListVerticalScrollIndicator = false
|
||||||
|
view.mainTableView.sr_addRefreshHeader { [weak self] in
|
||||||
|
self?.handleHeaderRefresh(nil)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var headerView: SRHomeHeaderView = {
|
||||||
|
let view = SRHomeHeaderView()
|
||||||
|
view.viewModel = self.viewModel
|
||||||
|
view.heightDidChange = { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.pageView.resizeTableHeaderViewHeight(animatable: false)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
deinit {
|
||||||
|
srPrint(message: "销毁")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
sr_setupUI()
|
||||||
|
|
||||||
|
Task {
|
||||||
|
await requestModuleList()
|
||||||
|
await requestCategoryList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
self.navigationController?.setNavigationBarHidden(true, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
|
||||||
|
Task {
|
||||||
|
await requestModuleList()
|
||||||
|
self.pageView.mainTableView.sr_endHeaderRefreshing()
|
||||||
|
}
|
||||||
|
if let vc = self.pageView.listContainerView.validListDict[self.pageView.listContainerView.currentIndex] as? SRViewController {
|
||||||
|
vc.handleHeaderRefresh(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHomeViewController {
|
||||||
|
|
||||||
|
func sr_setupUI() {
|
||||||
|
view.addSubview(searchButton)
|
||||||
|
view.addSubview(titleImageView)
|
||||||
|
titleImageView.addSubview(titleLabel)
|
||||||
|
view.addSubview(pageView)
|
||||||
|
menuContentView.addSubview(menuView)
|
||||||
|
menuContentView.addSubview(moreButton)
|
||||||
|
moreButton.addSubview(moreTitleLabel)
|
||||||
|
moreButton.addSubview(moreIconImageView)
|
||||||
|
|
||||||
|
menuView.listContainer = pageView.listContainerView
|
||||||
|
|
||||||
|
searchButton.snp.makeConstraints { make in
|
||||||
|
make.height.equalTo(44)
|
||||||
|
make.right.equalToSuperview()
|
||||||
|
make.top.equalToSuperview().offset(UIScreen.safeTop)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleImageView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.top.equalToSuperview().offset(UIScreen.safeTop + 12)
|
||||||
|
make.width.equalTo(UIScreen.getRatioWidth(size: titleImageView.image?.size.width ?? 0))
|
||||||
|
make.height.equalTo(UIScreen.getRatioWidth(size: titleImageView.image?.size.height ?? 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(UIScreen.getRatioWidth(size: 15))
|
||||||
|
make.centerY.equalTo(titleImageView.snp.top).offset(UIScreen.getRatioWidth(size: 21))
|
||||||
|
}
|
||||||
|
|
||||||
|
pageView.snp.makeConstraints { make in
|
||||||
|
make.left.right.bottom.equalToSuperview()
|
||||||
|
make.top.equalTo(titleImageView.snp.bottom).offset(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
menuView.snp.makeConstraints { make in
|
||||||
|
make.left.right.bottom.equalToSuperview()
|
||||||
|
make.height.equalTo(40)
|
||||||
|
}
|
||||||
|
|
||||||
|
moreButton.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
make.height.equalTo(40)
|
||||||
|
make.top.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
moreTitleLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.left.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
moreIconImageView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.right.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: JXPagingSmoothViewDataSource
|
||||||
|
extension SRHomeViewController: JXPagingViewDelegate {
|
||||||
|
func tableHeaderViewHeight(in pagingView: JXPagingView) -> Int {
|
||||||
|
return Int(ceil(self.headerView.contentHeight))
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableHeaderView(in pagingView: JXPagingView) -> UIView {
|
||||||
|
return self.headerView
|
||||||
|
}
|
||||||
|
|
||||||
|
func heightForPinSectionHeader(in pagingView: JXPagingView) -> Int {
|
||||||
|
return 90
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewForPinSectionHeader(in pagingView: JXPagingView) -> UIView {
|
||||||
|
return self.menuContentView
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberOfLists(in pagingView: JXPagingView) -> Int {
|
||||||
|
return self.menuDataSource.titles.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func pagingView(_ pagingView: JXPagingView, initListAtIndex index: Int) -> any JXPagingViewListViewDelegate {
|
||||||
|
let vc = SRHomeChildViewController()
|
||||||
|
vc.categoryId = self.viewModel.categoryArr[index].id
|
||||||
|
return vc
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: JXPagingMainTableViewGestureDelegate
|
||||||
|
extension SRHomeViewController: JXPagingMainTableViewGestureDelegate {
|
||||||
|
func mainTableViewGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if otherGestureRecognizer == menuView.collectionView.panGestureRecognizer {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if let view = otherGestureRecognizer.view {
|
||||||
|
var superview: UIView? = view.superview
|
||||||
|
while superview != nil {
|
||||||
|
if superview?.isKind(of: SRHomeHeaderView.self) == true {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
superview = superview?.superview
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gestureRecognizer.isKind(of: UIPanGestureRecognizer.self) && otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRHomeViewController {
|
||||||
|
|
||||||
|
func requestCategoryList() async {
|
||||||
|
await self.viewModel.requestCategoryList()
|
||||||
|
|
||||||
|
self.menuDataSource.titles = self.viewModel.categoryTitleArr
|
||||||
|
|
||||||
|
self.pageView.reloadData()
|
||||||
|
self.menuView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestModuleList() async {
|
||||||
|
await self.viewModel.requestModuleList()
|
||||||
|
|
||||||
|
self.headerView.reloadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
113
SynthReel/Class/Home/VC/SRSearchViewController.swift
Normal file
113
SynthReel/Class/Home/VC/SRSearchViewController.swift
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
//
|
||||||
|
// SRSearchViewController.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRSearchViewController: SRViewController {
|
||||||
|
|
||||||
|
lazy var viewModel = SRSearchViewModel()
|
||||||
|
|
||||||
|
lazy var returnButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.sr_handleNavigationBack()
|
||||||
|
}))
|
||||||
|
button.setImage(UIImage(named: "arrow_left_icon_01"), for: .normal)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var textView: SRSearchTextView = {
|
||||||
|
let view = SRSearchTextView()
|
||||||
|
view.didSearch = { [weak self] text in
|
||||||
|
self?.search(text)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var homeView: SRSearchHomeView = {
|
||||||
|
let view = SRSearchHomeView()
|
||||||
|
view.viewModel = self.viewModel
|
||||||
|
view.didSearch = { [weak self] text in
|
||||||
|
self?.textView.text = text
|
||||||
|
self?.search(text)
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var resultView: SRSearchResultView = {
|
||||||
|
let view = SRSearchResultView()
|
||||||
|
view.viewModel = self.viewModel
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
self.resultView.isHidden = true
|
||||||
|
|
||||||
|
Task {
|
||||||
|
await self.viewModel.requestHotSearchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
self.navigationController?.setNavigationBarHidden(true, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func search(_ text: String) {
|
||||||
|
if text.isEmpty {
|
||||||
|
homeView.isHidden = false
|
||||||
|
resultView.isHidden = true
|
||||||
|
} else {
|
||||||
|
textView.resignFirstResponder()
|
||||||
|
homeView.isHidden = true
|
||||||
|
resultView.isHidden = false
|
||||||
|
resultView.search(text)
|
||||||
|
self.viewModel.addSearchRecord(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRSearchViewController {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
view.addSubview(returnButton)
|
||||||
|
view.addSubview(textView)
|
||||||
|
view.addSubview(homeView)
|
||||||
|
view.addSubview(resultView)
|
||||||
|
|
||||||
|
|
||||||
|
returnButton.snp.makeConstraints { make in
|
||||||
|
make.width.height.equalTo(44)
|
||||||
|
make.left.equalToSuperview().offset(10)
|
||||||
|
make.top.equalToSuperview().offset(UIScreen.safeTop)
|
||||||
|
}
|
||||||
|
|
||||||
|
textView.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(returnButton.snp.right).offset(10)
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
make.centerY.equalTo(returnButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
homeView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
make.top.equalTo(returnButton.snp.bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalTo(homeView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
36
SynthReel/Class/Home/VM/SRHomeMenuDataSource.swift
Normal file
36
SynthReel/Class/Home/VM/SRHomeMenuDataSource.swift
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// SRHomeMenuDataSource.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/14.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import JXSegmentedView
|
||||||
|
|
||||||
|
|
||||||
|
class SRHomeMenuDataSource: JXSegmentedTitleGradientDataSource {
|
||||||
|
|
||||||
|
nonisolated override init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
nonisolated override func registerCellClass(in segmentedView: JXSegmentedView) {
|
||||||
|
MainActor.assumeIsolated {
|
||||||
|
segmentedView.collectionView.register(SRHomeMenuCell.self, forCellWithReuseIdentifier: "SRHomeMenuCell")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nonisolated override func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell {
|
||||||
|
return MainActor.assumeIsolated {
|
||||||
|
let cell = segmentedView.dequeueReusableCell(withReuseIdentifier: "SRHomeMenuCell", at: index)
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
107
SynthReel/Class/Home/VM/SRHomeViewModel.swift
Normal file
107
SynthReel/Class/Home/VM/SRHomeViewModel.swift
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// SRHomeViewModel.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/14.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRHomeViewModel: NSObject {
|
||||||
|
|
||||||
|
lazy var categoryArr: [SRCategoryModel] = []
|
||||||
|
lazy var categoryTitleArr: [String] = []
|
||||||
|
|
||||||
|
lazy var moduleArr: [SRHomeModuleItem] = []
|
||||||
|
|
||||||
|
func requestCategoryList() async {
|
||||||
|
if let list = await SRHomeApi.requestCategoryList() {
|
||||||
|
self.categoryArr = list
|
||||||
|
var titles: [String] = []
|
||||||
|
self.categoryArr.forEach {
|
||||||
|
titles.append($0.name ?? "")
|
||||||
|
}
|
||||||
|
self.categoryTitleArr = titles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestModuleList() async {
|
||||||
|
guard let list = await SRHomeApi.requestHomeModulesData() else { return }
|
||||||
|
|
||||||
|
/*
|
||||||
|
第一项 get_details_recommand
|
||||||
|
popular = home_v3_recommand
|
||||||
|
updates = week_ranking
|
||||||
|
Binge-Worthy = week_highest_recommend
|
||||||
|
Viral Hits = highest_payment_hot_video
|
||||||
|
Premiere Now = new_recommand
|
||||||
|
*/
|
||||||
|
|
||||||
|
var bannerItem: SRHomeModuleItem? = nil
|
||||||
|
var detailsRecommandItem: SRHomeModuleItem? = nil
|
||||||
|
var popularItem: SRHomeModuleItem? = nil
|
||||||
|
var updatesItem: SRHomeModuleItem? = nil
|
||||||
|
var bingeWorthyItem: SRHomeModuleItem? = nil
|
||||||
|
var viralHitsItem: SRHomeModuleItem? = nil
|
||||||
|
var premiereNowItem: SRHomeModuleItem? = nil
|
||||||
|
|
||||||
|
|
||||||
|
list.forEach {
|
||||||
|
switch $0.module_key {
|
||||||
|
case .banner:
|
||||||
|
bannerItem = $0
|
||||||
|
|
||||||
|
case .detailsRecommand:
|
||||||
|
detailsRecommandItem = $0
|
||||||
|
|
||||||
|
case .popular:
|
||||||
|
popularItem = $0
|
||||||
|
|
||||||
|
case .updates:
|
||||||
|
updatesItem = $0
|
||||||
|
|
||||||
|
case .bingeWorthy:
|
||||||
|
bingeWorthyItem = $0
|
||||||
|
|
||||||
|
case .viralHits:
|
||||||
|
viralHitsItem = $0
|
||||||
|
|
||||||
|
case .premiereNow:
|
||||||
|
premiereNowItem = $0
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleArr.removeAll()
|
||||||
|
|
||||||
|
if let item = bannerItem {
|
||||||
|
moduleArr.append(item)
|
||||||
|
}
|
||||||
|
if let item = detailsRecommandItem {
|
||||||
|
moduleArr.append(item)
|
||||||
|
}
|
||||||
|
if let item = popularItem {
|
||||||
|
moduleArr.append(item)
|
||||||
|
}
|
||||||
|
if let item = updatesItem {
|
||||||
|
moduleArr.append(item)
|
||||||
|
}
|
||||||
|
if let item = bingeWorthyItem {
|
||||||
|
moduleArr.append(item)
|
||||||
|
}
|
||||||
|
if let item = viralHitsItem {
|
||||||
|
moduleArr.append(item)
|
||||||
|
}
|
||||||
|
if let item = premiereNowItem {
|
||||||
|
moduleArr.append(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
53
SynthReel/Class/Home/VM/SRSearchViewModel.swift
Normal file
53
SynthReel/Class/Home/VM/SRSearchViewModel.swift
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// SRSearchViewModel.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRSearchViewModel: NSObject {
|
||||||
|
|
||||||
|
static let searchRecordUserDefaultKey = "SRSearchViewModel.searchRecordUserDefaultKey"
|
||||||
|
|
||||||
|
@objc dynamic private(set) var recordList: [String] = (UserDefaults.standard.object(forKey: SRSearchViewModel.searchRecordUserDefaultKey) as? [String]) ?? []
|
||||||
|
|
||||||
|
@objc dynamic private(set) lazy var hotDataArr: [SRShortModel] = []
|
||||||
|
|
||||||
|
func addSearchRecord(_ text: String) {
|
||||||
|
guard !text.isEmpty else { return }
|
||||||
|
var list = recordList
|
||||||
|
|
||||||
|
for (index, value) in list.enumerated() {
|
||||||
|
if value == text {
|
||||||
|
list.remove(at: index)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.insert(text, at: 0)
|
||||||
|
|
||||||
|
if list.count > 10 {
|
||||||
|
list.removeLast()
|
||||||
|
}
|
||||||
|
recordList = list
|
||||||
|
|
||||||
|
UserDefaults.standard.set(list, forKey: SRSearchViewModel.searchRecordUserDefaultKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearSearchRecord() {
|
||||||
|
recordList.removeAll()
|
||||||
|
UserDefaults.standard.set(recordList, forKey: SRSearchViewModel.searchRecordUserDefaultKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
///获取热门搜索
|
||||||
|
func requestHotSearchData() async {
|
||||||
|
if let list = await SRHomeApi.requestHotSearchData(), list.count > 0 {
|
||||||
|
self.hotDataArr = list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
37
SynthReel/Class/MyShort/VC/SRMyShortViewController.swift
Normal file
37
SynthReel/Class/MyShort/VC/SRMyShortViewController.swift
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// SRMyShortViewController.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/20.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRMyShortViewController: SRViewController {
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
self.backgroundImageView.image = UIImage(named: "my_short_bg_image")
|
||||||
|
self.backgroundImageView.contentMode = .scaleAspectFit
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
self.navigationController?.setNavigationBarHidden(true, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRMyShortViewController {
|
||||||
|
private func sr_setupUI() {
|
||||||
|
|
||||||
|
self.backgroundImageView.snp.remakeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
SynthReel/Class/Player/M/SRShortDetailModel.swift
Normal file
20
SynthReel/Class/Player/M/SRShortDetailModel.swift
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// SRShortDetailModel.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SmartCodable
|
||||||
|
|
||||||
|
class SRShortDetailModel: NSObject, SmartCodable {
|
||||||
|
required override init() { }
|
||||||
|
|
||||||
|
var episodeList: [SRVideoInfoModel]?
|
||||||
|
var video_info: SRVideoInfoModel?
|
||||||
|
var shortPlayInfo: SRShortModel?
|
||||||
|
var is_collect: Bool?
|
||||||
|
var share_coin: Int?
|
||||||
|
}
|
||||||
58
SynthReel/Class/Player/M/SRShortModel.swift
Normal file
58
SynthReel/Class/Player/M/SRShortModel.swift
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// SRShortModel.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/14.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SmartCodable
|
||||||
|
|
||||||
|
class SRShortModel: NSObject, SmartCodable {
|
||||||
|
|
||||||
|
required override init() { }
|
||||||
|
|
||||||
|
var id: String?
|
||||||
|
var sr_description: String?
|
||||||
|
var name: String?
|
||||||
|
var watch_total: Int?
|
||||||
|
var collect_total: Int?
|
||||||
|
var current_episode: String?
|
||||||
|
var short_play_video_id: String?
|
||||||
|
var image_url: String?
|
||||||
|
var is_collect: Bool?
|
||||||
|
var episode_total: Int?
|
||||||
|
var horizontally_img: String?
|
||||||
|
var category: [String]?
|
||||||
|
var video_url: String?
|
||||||
|
var categoryList: [SRCategoryModel]?
|
||||||
|
var short_play_id: String?
|
||||||
|
var video_info: SRVideoInfoModel?
|
||||||
|
|
||||||
|
@SmartIgnored
|
||||||
|
var cellHeight: CGFloat = 0
|
||||||
|
|
||||||
|
|
||||||
|
static func mappingForKey() -> [SmartKeyTransformer]? {
|
||||||
|
return [
|
||||||
|
CodingKeys.sr_description <--- ["description", "short_video_description"],
|
||||||
|
CodingKeys.name <--- ["short_video_title", "name"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SRVideoInfoModel: NSObject, SmartCodable {
|
||||||
|
required override init() { }
|
||||||
|
|
||||||
|
var episode: String?
|
||||||
|
var short_play_id: String?
|
||||||
|
var coins: Int?
|
||||||
|
var video_url: String?
|
||||||
|
///是否锁定,购买后解锁
|
||||||
|
var is_lock: Bool?
|
||||||
|
var short_play_video_id: String?
|
||||||
|
///播放进度,毫秒
|
||||||
|
var play_seconds: Int?
|
||||||
|
var image_url: String?
|
||||||
|
}
|
||||||
91
SynthReel/Class/Player/V/SREpSelectorCell.swift
Normal file
91
SynthReel/Class/Player/V/SREpSelectorCell.swift
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
//
|
||||||
|
// SREpSelectorCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/19.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SREpSelectorCell: UICollectionViewCell {
|
||||||
|
|
||||||
|
|
||||||
|
var model: SRVideoInfoModel? {
|
||||||
|
didSet {
|
||||||
|
numLabel.text = model?.episode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sr_isSelected: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if sr_isSelected {
|
||||||
|
numLabel.textColors = [UIColor.srGreen.cgColor, UIColor.srBlue.cgColor]
|
||||||
|
boderView.isHidden = false
|
||||||
|
} else {
|
||||||
|
numLabel.textColors = [UIColor.white.cgColor, UIColor.white.cgColor]
|
||||||
|
boderView.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var numLabel: SRLabel = {
|
||||||
|
let label = SRLabel()
|
||||||
|
label.font = .font(ofSize: 14, weight: .regular)
|
||||||
|
label.textStartPoint = .init(x: 0.5, y: 0)
|
||||||
|
label.textEndPoint = .init(x: 0.5, y: 1)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var boderView: SRGradientView = {
|
||||||
|
let view = SRGradientView()
|
||||||
|
view.colors = [UIColor.srGreen.cgColor, UIColor.srBlue.cgColor]
|
||||||
|
view.startPoint = .init(x: 0.5, y: 0)
|
||||||
|
view.endPoint = .init(x: 0.5, y: 1)
|
||||||
|
view.layer.cornerRadius = 10
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var boderLayer: CAShapeLayer = {
|
||||||
|
let layer = CAShapeLayer()
|
||||||
|
return layer
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
contentView.layer.cornerRadius = 10
|
||||||
|
contentView.layer.masksToBounds = true
|
||||||
|
contentView.backgroundColor = ._1_B_1_B_1_B
|
||||||
|
boderLayer.fillColor = contentView.backgroundColor?.cgColor // 设置为透明填充,实现镂空效果
|
||||||
|
|
||||||
|
|
||||||
|
contentView.addSubview(boderView)
|
||||||
|
boderView.layer.addSublayer(boderLayer)
|
||||||
|
contentView.addSubview(numLabel)
|
||||||
|
|
||||||
|
numLabel.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
boderView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
let size = self.bounds.size
|
||||||
|
let boderWidth: CGFloat = 1
|
||||||
|
|
||||||
|
boderLayer.path = UIBezierPath(roundedRect: .init(x: boderWidth, y: boderWidth, width: size.width - boderWidth * 2, height: size.height - boderWidth * 2), cornerRadius: 9.5).cgPath
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
197
SynthReel/Class/Player/V/SREpSelectorView.swift
Normal file
197
SynthReel/Class/Player/V/SREpSelectorView.swift
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
//
|
||||||
|
// SREpSelectorView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import HWPanModal
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SREpSelectorView: SRPanModalContentView {
|
||||||
|
|
||||||
|
var didSelected: ((_ index: Int) -> Void)?
|
||||||
|
|
||||||
|
var model: SRShortDetailModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sr_setImage(model?.shortPlayInfo?.image_url)
|
||||||
|
shortNameLabel.text = model?.shortPlayInfo?.name
|
||||||
|
desLabel.text = model?.shortPlayInfo?.sr_description
|
||||||
|
|
||||||
|
subtitleLabel.text = "all_episodes_text".localizedReplace(text: "\(model?.shortPlayInfo?.episode_total ?? 0)")
|
||||||
|
|
||||||
|
if let text = model?.shortPlayInfo?.category?.first, text.count > 0 {
|
||||||
|
cagetoryLabel.text = "#" + text
|
||||||
|
} else {
|
||||||
|
cagetoryLabel.text = ""
|
||||||
|
}
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var selectedIndex: Int = 0 {
|
||||||
|
didSet {
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var coverBgView = UIImageView(image: UIImage(named: "ep_cover_bg_image"))
|
||||||
|
lazy var coverImageView: UIImageView = {
|
||||||
|
let imageView = SRImageView()
|
||||||
|
imageView.layer.cornerRadius = 2
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var shortNameLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 15, weight: .semibold)
|
||||||
|
label.textColor = .srBlue
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var cagetoryLabel: SRLabel = {
|
||||||
|
let label = SRLabel()
|
||||||
|
label.font = .font(ofSize: 12, weight: .regular)
|
||||||
|
label.textColors = [UIColor.srGreen.cgColor, UIColor.srBlue.cgColor]
|
||||||
|
label.textStartPoint = .init(x: 0.5, y: 0)
|
||||||
|
label.textEndPoint = .init(x: 0.5, y: 1)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var desLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 12, weight: .regular)
|
||||||
|
label.textColor = .A_6_A_6_A_6
|
||||||
|
label.numberOfLines = 3
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 15, weight: .medium)
|
||||||
|
label.textColor = .white
|
||||||
|
label.text = "Select Episode".localized
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var subtitleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 12, weight: .regular)
|
||||||
|
label.textColor = .CCCCCC
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let itemWidth = (UIScreen.width - 30 - 40) / 5
|
||||||
|
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.minimumLineSpacing = 10
|
||||||
|
layout.minimumInteritemSpacing = 10
|
||||||
|
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
|
||||||
|
layout.itemSize = .init(width: floor(itemWidth), height: 50)
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectionView: SRCollectionView = {
|
||||||
|
let collectionView = SRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
collectionView.showsVerticalScrollIndicator = false
|
||||||
|
collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.safeBottom + 10, right: 0)
|
||||||
|
collectionView.register(SREpSelectorCell.self, forCellWithReuseIdentifier: "cell")
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension SREpSelectorView {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
addSubview(coverBgView)
|
||||||
|
addSubview(coverImageView)
|
||||||
|
addSubview(shortNameLabel)
|
||||||
|
addSubview(cagetoryLabel)
|
||||||
|
addSubview(desLabel)
|
||||||
|
addSubview(titleLabel)
|
||||||
|
addSubview(subtitleLabel)
|
||||||
|
addSubview(collectionView)
|
||||||
|
|
||||||
|
coverBgView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.top.equalToSuperview().offset(18)
|
||||||
|
}
|
||||||
|
|
||||||
|
coverImageView.snp.makeConstraints { make in
|
||||||
|
make.center.equalTo(coverBgView)
|
||||||
|
make.width.equalTo(63)
|
||||||
|
make.height.equalTo(84)
|
||||||
|
}
|
||||||
|
|
||||||
|
shortNameLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(coverImageView.snp.right).offset(10)
|
||||||
|
make.top.equalToSuperview().offset(24)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||||
|
}
|
||||||
|
|
||||||
|
cagetoryLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(shortNameLabel)
|
||||||
|
make.top.equalTo(shortNameLabel.snp.bottom).offset(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
desLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(shortNameLabel)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-15)
|
||||||
|
make.top.equalTo(shortNameLabel.snp.bottom).offset(32)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.top.equalTo(coverBgView.snp.bottom).offset(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
subtitleLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalTo(titleLabel)
|
||||||
|
make.left.equalTo(titleLabel.snp.right).offset(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
collectionView.snp.makeConstraints { make in
|
||||||
|
make.left.right.equalToSuperview()
|
||||||
|
make.top.equalToSuperview().offset(166)
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: UICollectionViewDelegate UICollectionViewDataSource
|
||||||
|
extension SREpSelectorView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SREpSelectorCell
|
||||||
|
cell.model = self.model?.episodeList?[indexPath.row]
|
||||||
|
cell.sr_isSelected = indexPath.row == self.selectedIndex
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return self.model?.episodeList?.count ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
self.didSelected?(indexPath.row)
|
||||||
|
Task {
|
||||||
|
await self.dismiss(animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
216
SynthReel/Class/Player/V/SRProgressView.swift
Normal file
216
SynthReel/Class/Player/V/SRProgressView.swift
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
//
|
||||||
|
// SRProgressView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import YYText
|
||||||
|
import YYCategories
|
||||||
|
|
||||||
|
class SRProgressView: UIView {
|
||||||
|
|
||||||
|
///滑动开始
|
||||||
|
var panStart: (() -> Void)?
|
||||||
|
|
||||||
|
///滑动中
|
||||||
|
var panChange: ((_ progress: CGFloat) -> Void)?
|
||||||
|
|
||||||
|
///滑动完成回调
|
||||||
|
var panFinish: ((_ progress: CGFloat) -> Void)?
|
||||||
|
|
||||||
|
var progress: CGFloat = 0 {
|
||||||
|
didSet {
|
||||||
|
if !isPaning {
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///用来记录滑动时的当前进度
|
||||||
|
private var tempProgress: CGFloat = 0
|
||||||
|
|
||||||
|
///滑动进度
|
||||||
|
private var panProgress: CGFloat = 0
|
||||||
|
|
||||||
|
var progressColor = UIColor.white.withAlphaComponent(0.2)
|
||||||
|
var currentProgress = UIColor.white
|
||||||
|
|
||||||
|
var lineWidth: CGFloat = 3
|
||||||
|
|
||||||
|
///加载中状态
|
||||||
|
var isLoading = false {
|
||||||
|
didSet {
|
||||||
|
if isLoading {
|
||||||
|
if gradientTimer == nil {
|
||||||
|
gradientTimer = Timer.scheduledTimer(timeInterval: 0.05, target: YYTextWeakProxy(target: self), selector: #selector(handleGradientTimer), userInfo: nil, repeats: true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gradientTimer?.invalidate()
|
||||||
|
gradientTimer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var insets: UIEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: 0) {
|
||||||
|
didSet {
|
||||||
|
self.invalidateIntrinsicContentSize()
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private(set) lazy var panGesture: UIPanGestureRecognizer = {
|
||||||
|
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(sender:)))
|
||||||
|
return pan
|
||||||
|
}()
|
||||||
|
|
||||||
|
private(set) lazy var tagGesture: UITapGestureRecognizer = {
|
||||||
|
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(sender:)))
|
||||||
|
return tap
|
||||||
|
}()
|
||||||
|
|
||||||
|
///是否在滑动中
|
||||||
|
private var isPaning: Bool = false
|
||||||
|
|
||||||
|
private var gradientTimer: Timer?
|
||||||
|
|
||||||
|
private var gradientValue: CGFloat = 0
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
return .init(width: UIScreen.width, height: lineWidth + insets.top + insets.bottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
// self.backgroundColor = progressColor
|
||||||
|
self.backgroundColor = .clear
|
||||||
|
|
||||||
|
self.addGestureRecognizer(panGesture)
|
||||||
|
self.addGestureRecognizer(tagGesture)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func handleGradientTimer() {
|
||||||
|
gradientValue += 0.1
|
||||||
|
if gradientValue > 1 {
|
||||||
|
gradientValue = 0
|
||||||
|
}
|
||||||
|
setNeedsDisplay()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func draw(_ rect: CGRect) {
|
||||||
|
super.draw(rect)
|
||||||
|
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||||
|
let width = rect.width
|
||||||
|
|
||||||
|
let progressX = insets.left
|
||||||
|
let progressY = insets.top
|
||||||
|
let progressWidth = width - insets.left - insets.right
|
||||||
|
|
||||||
|
if isLoading, !isPaning {
|
||||||
|
// 定义颜色空间
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let colors: [CGColor] = [
|
||||||
|
UIColor.clear.cgColor,
|
||||||
|
UIColor.white.cgColor,
|
||||||
|
UIColor.clear.cgColor
|
||||||
|
]
|
||||||
|
let locations: [CGFloat] = [0.0, gradientValue, 1.0]
|
||||||
|
|
||||||
|
guard let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: locations) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let gradientRect = CGRect(x: progressX,
|
||||||
|
y: progressY,
|
||||||
|
width: progressWidth,
|
||||||
|
height: lineWidth)
|
||||||
|
|
||||||
|
// 定义渐变的起点和终点
|
||||||
|
let startPoint = CGPoint(x: rect.minX, y: rect.minY)
|
||||||
|
let endPoint = CGPoint(x: rect.maxX, y: rect.maxY)
|
||||||
|
|
||||||
|
// 裁剪到渐变区域
|
||||||
|
context.saveGState()
|
||||||
|
context.clip(to: gradientRect)
|
||||||
|
|
||||||
|
// 绘制渐变
|
||||||
|
context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: [])
|
||||||
|
} else {
|
||||||
|
var progress = self.progress
|
||||||
|
if self.isPaning {
|
||||||
|
progress = self.panProgress
|
||||||
|
}
|
||||||
|
let currentProgressWidth = progressWidth * progress
|
||||||
|
|
||||||
|
///绘制进度
|
||||||
|
let progressPath = UIBezierPath(roundedRect: CGRect(x: progressX, y: progressY, width: progressWidth, height: lineWidth), cornerRadius: lineWidth / 2)
|
||||||
|
context.addPath(progressPath.cgPath)
|
||||||
|
context.setFillColor(progressColor.cgColor)
|
||||||
|
context.fillPath()
|
||||||
|
|
||||||
|
///绘制当前进度
|
||||||
|
let currentPath = UIBezierPath(roundedRect: CGRect(x: progressX, y: progressY, width: progressWidth * progress, height: lineWidth), cornerRadius: lineWidth / 2)
|
||||||
|
context.addPath(currentPath.cgPath)
|
||||||
|
context.setFillColor(currentProgress.cgColor)
|
||||||
|
context.fillPath()
|
||||||
|
|
||||||
|
///绘制一个点
|
||||||
|
let path = UIBezierPath(arcCenter: .init(x: currentProgressWidth + progressX, y: progressY + lineWidth / 2), radius: 3, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
|
||||||
|
context.addPath(path.cgPath)
|
||||||
|
context.setFillColor(currentProgress.cgColor)
|
||||||
|
context.fillPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRProgressView {
|
||||||
|
@objc func handlePanGesture(sender: UIPanGestureRecognizer) {
|
||||||
|
|
||||||
|
switch sender.state {
|
||||||
|
case .began:
|
||||||
|
self.isPaning = true
|
||||||
|
self.tempProgress = self.progress
|
||||||
|
sender.setTranslation(CGPoint(x: 0, y: 0), in: self)
|
||||||
|
self.panStart?()
|
||||||
|
|
||||||
|
case .changed:
|
||||||
|
let point = sender.translation(in: self)
|
||||||
|
let offsetX = point.x / (self.width - self.insets.left - self.insets.right)
|
||||||
|
self.panProgress = self.tempProgress + offsetX
|
||||||
|
if self.panProgress < 0 {
|
||||||
|
self.panProgress = 0
|
||||||
|
}
|
||||||
|
self.panChange?(self.panProgress)
|
||||||
|
setNeedsDisplay()
|
||||||
|
|
||||||
|
default:
|
||||||
|
self.isPaning = false
|
||||||
|
self.panFinish?(self.panProgress)
|
||||||
|
|
||||||
|
self.panProgress = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func handleTapGesture(sender: UITapGestureRecognizer) {
|
||||||
|
let point = sender.location(in: self)
|
||||||
|
let offsetX = (point.x - self.insets.left) / (self.width - self.insets.left - self.insets.right)
|
||||||
|
self.panFinish?(offsetX)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
SynthReel/Class/Player/V/SRRecommendPlayerCell.swift
Normal file
26
SynthReel/Class/Player/V/SRRecommendPlayerCell.swift
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// SRRecommendPlayerCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/20.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import JXPlayer
|
||||||
|
|
||||||
|
class SRRecommendPlayerCell: JXPlayerListCell {
|
||||||
|
|
||||||
|
override var ControlViewClass: JXPlayerListControlView.Type {
|
||||||
|
return SRRecommendPlayerControlView.self
|
||||||
|
}
|
||||||
|
|
||||||
|
override var model: Any? {
|
||||||
|
didSet {
|
||||||
|
let model = self.model as? SRShortModel
|
||||||
|
let videoInfo = model?.video_info
|
||||||
|
self.player.setPlayUrl(url: videoInfo?.video_url ?? "")
|
||||||
|
self.player.coverImageView?.sr_setImage(model?.image_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
226
SynthReel/Class/Player/V/SRRecommendPlayerControlView.swift
Normal file
226
SynthReel/Class/Player/V/SRRecommendPlayerControlView.swift
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
//
|
||||||
|
// SRRecommendPlayerControlView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/20.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import JXPlayer
|
||||||
|
import SnapKit
|
||||||
|
import YYCategories
|
||||||
|
|
||||||
|
class SRRecommendPlayerControlView: JXPlayerListControlView {
|
||||||
|
|
||||||
|
override var viewModel: JXPlayerListViewModel? {
|
||||||
|
didSet {
|
||||||
|
self.viewModel?.addObserver(self, forKeyPath: "isPlaying", context: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var model: Any? {
|
||||||
|
didSet {
|
||||||
|
let model = model as! SRShortModel
|
||||||
|
|
||||||
|
shortNameLabel.text = model.name
|
||||||
|
|
||||||
|
stackView.sr_removeAllArrangedSubview()
|
||||||
|
if let text = model.category?.first, text.count > 0 {
|
||||||
|
categoryLabel.text = "#" + text
|
||||||
|
stackView.addArrangedSubview(categoryLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let text = model.sr_description, text.count > 0 {
|
||||||
|
desLabel.text = text
|
||||||
|
stackView.addArrangedSubview(desLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var isCurrent: Bool {
|
||||||
|
didSet {
|
||||||
|
updatePlayerViewStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var controlerView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "short_progress_bg_image"))
|
||||||
|
imageView.isUserInteractionEnabled = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var shortNameLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 14, weight: .semibold)
|
||||||
|
label.textColor = .srBlue
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var stackView: UIStackView = {
|
||||||
|
let view = UIStackView()
|
||||||
|
view.axis = .vertical
|
||||||
|
view.spacing = 8
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var categoryLabel: SRLabel = {
|
||||||
|
let label = SRLabel()
|
||||||
|
label.font = .font(ofSize: 11, weight: .regular)
|
||||||
|
label.textColors = [UIColor.srGreen.cgColor, UIColor.srBlue.cgColor]
|
||||||
|
label.textStartPoint = .init(x: 0.5, y: 0)
|
||||||
|
label.textEndPoint = .init(x: 0.5, y: 1)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var desLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 11, weight: .regular)
|
||||||
|
label.textColor = .A_6_A_6_A_6
|
||||||
|
label.numberOfLines = 2
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var epBgView: UIView = {
|
||||||
|
let view = SRGradientView()
|
||||||
|
view.colors = [UIColor._51_D_4_FF.withAlphaComponent(0.5).cgColor, UIColor._4_CFFD_4.withAlphaComponent(0.1).cgColor]
|
||||||
|
view.startPoint = .init(x: 0, y: 0.5)
|
||||||
|
view.endPoint = .init(x: 1, y: 0.5)
|
||||||
|
view.layer.cornerRadius = 2
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
let tap = UITapGestureRecognizer { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let vc = SRDetailPlayerViewController()
|
||||||
|
vc.shortId = (self.model as? SRShortModel)?.short_play_id
|
||||||
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
|
}
|
||||||
|
view.addGestureRecognizer(tap)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var epIconImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "ep_icon_02"))
|
||||||
|
imageView.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
imageView.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var epTextLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 12, weight: .regular)
|
||||||
|
label.textColor = .white
|
||||||
|
label.text = "recommend_ep_text".localized
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var indicatorImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "arrow_right_icon_02"))
|
||||||
|
imageView.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
|
imageView.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var playerImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "play_icon_02"))
|
||||||
|
imageView.isHidden = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.viewModel?.removeObserver(self, forKeyPath: "isPlaying")
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
sr_setupUI()
|
||||||
|
|
||||||
|
let tap = UITapGestureRecognizer { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.viewModel?.userSwitchPlayAndPause()
|
||||||
|
}
|
||||||
|
self.addGestureRecognizer(tap)
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
if keyPath == "isPlaying" {
|
||||||
|
updatePlayerViewStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func updatePlayerViewStatus() {
|
||||||
|
if self.viewModel?.isPlaying == true || !isCurrent {
|
||||||
|
playerImageView.isHidden = true
|
||||||
|
} else {
|
||||||
|
playerImageView.isHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRRecommendPlayerControlView {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
addSubview(controlerView)
|
||||||
|
controlerView.addSubview(shortNameLabel)
|
||||||
|
controlerView.addSubview(stackView)
|
||||||
|
controlerView.addSubview(epBgView)
|
||||||
|
epBgView.addSubview(epIconImageView)
|
||||||
|
epBgView.addSubview(epTextLabel)
|
||||||
|
epBgView.addSubview(indicatorImageView)
|
||||||
|
addSubview(playerImageView)
|
||||||
|
|
||||||
|
controlerView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview().offset(-10)
|
||||||
|
}
|
||||||
|
|
||||||
|
shortNameLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(12)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-12)
|
||||||
|
make.top.equalToSuperview().offset(13)
|
||||||
|
}
|
||||||
|
|
||||||
|
stackView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(12)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-12)
|
||||||
|
make.top.equalTo(shortNameLabel.snp.bottom).offset(8)
|
||||||
|
make.bottom.equalToSuperview().offset(-52)
|
||||||
|
}
|
||||||
|
|
||||||
|
epBgView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(12)
|
||||||
|
make.right.equalToSuperview().offset(-12)
|
||||||
|
make.bottom.equalToSuperview().offset(-18)
|
||||||
|
make.height.equalTo(26)
|
||||||
|
}
|
||||||
|
|
||||||
|
epIconImageView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.left.equalToSuperview().offset(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
epTextLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.left.equalTo(epIconImageView.snp.right).offset(4)
|
||||||
|
make.right.lessThanOrEqualTo(self.indicatorImageView.snp.left).offset(-5)
|
||||||
|
}
|
||||||
|
|
||||||
|
indicatorImageView.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalToSuperview()
|
||||||
|
make.right.equalToSuperview().offset(-12)
|
||||||
|
}
|
||||||
|
|
||||||
|
playerImageView.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
239
SynthReel/Class/Player/V/SRShortDetailControlView.swift
Normal file
239
SynthReel/Class/Player/V/SRShortDetailControlView.swift
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
//
|
||||||
|
// SRShortDetailControlView.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import JXPlayer
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRShortDetailControlView: JXPlayerListControlView {
|
||||||
|
|
||||||
|
var sr_viewModel: SRShortPlayerViewModel? {
|
||||||
|
return self.viewModel as? SRShortPlayerViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
override var viewModel: JXPlayerListViewModel? {
|
||||||
|
didSet {
|
||||||
|
self.viewModel?.addObserver(self, forKeyPath: "isPlaying", context: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortModel: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
titleLabel.text = shortModel?.name
|
||||||
|
collectButton.isSelected = shortModel?.is_collect == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var durationTime: TimeInterval {
|
||||||
|
didSet {
|
||||||
|
updateProgress()
|
||||||
|
let (_, m, s) = Int(durationTime).formatTimeGroup()
|
||||||
|
totalTimeLabel.text = "\(m):\(s)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var currentTime: TimeInterval {
|
||||||
|
didSet {
|
||||||
|
updateProgress()
|
||||||
|
let (_, m, s) = Int(currentTime).formatTimeGroup()
|
||||||
|
currentTimeLabel.text = "\(m):\(s)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var isLoading: Bool {
|
||||||
|
didSet {
|
||||||
|
progressView.isLoading = isLoading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var progressBgView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "short_progress_bg_image"))
|
||||||
|
imageView.isUserInteractionEnabled = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var titleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 14, weight: .medium)
|
||||||
|
label.textColor = .srBlue
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var progressView: SRProgressView = {
|
||||||
|
let view = SRProgressView()
|
||||||
|
view.insets = .init(top: 10, left: 5, bottom: 10, right: 5)
|
||||||
|
view.panFinish = { [weak self] progress in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.viewModel?.seekTo(Float(progress))
|
||||||
|
}
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var totalTimeLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 10, weight: .regular)
|
||||||
|
label.textColor = .DFDFDF
|
||||||
|
label.text = "00:00"
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var currentTimeLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .font(ofSize: 10, weight: .regular)
|
||||||
|
label.textColor = .DFDFDF
|
||||||
|
label.text = "00:00"
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var epButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.sr_viewModel?.onEpSelectorView()
|
||||||
|
}))
|
||||||
|
button.setImage(UIImage(named: "ep_icon_01"), for: .normal)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard let shortId = self.shortModel?.short_play_id else { return }
|
||||||
|
let videoId = (self.model as? SRVideoInfoModel)?.short_play_video_id
|
||||||
|
let isCollect = !(self.shortModel?.is_collect ?? false)
|
||||||
|
|
||||||
|
Task {
|
||||||
|
await SRShortApi.requestShortCollect(shortId: shortId, videoId: videoId, isCollect: isCollect)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
button.setImage(UIImage(named: "collect_icon_01"), for: .normal)
|
||||||
|
button.setImage(UIImage(named: "collect_icon_01_selected"), for: .selected)
|
||||||
|
button.setImage(UIImage(named: "collect_icon_01_selected"), for: [.selected, .highlighted])
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var playerImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "play_icon_02"))
|
||||||
|
imageView.isHidden = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.viewModel?.removeObserver(self, forKeyPath: "isPlaying")
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(updateShortCollectStateNotification), name: SRShortApi.updateShortCollectStateNotification, object: nil)
|
||||||
|
|
||||||
|
let tap = UITapGestureRecognizer { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.viewModel?.userSwitchPlayAndPause()
|
||||||
|
}
|
||||||
|
self.addGestureRecognizer(tap)
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func updateShortCollectStateNotification(sender: Notification) {
|
||||||
|
guard let userInfo = sender.userInfo else { return }
|
||||||
|
guard let shortId = userInfo["id"] as? String else { return }
|
||||||
|
guard let state = userInfo["state"] as? Bool else { return }
|
||||||
|
guard shortId == self.shortModel?.short_play_id else { return }
|
||||||
|
self.shortModel?.is_collect = state
|
||||||
|
|
||||||
|
collectButton.isSelected = state
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateProgress() {
|
||||||
|
guard durationTime > 0 else {
|
||||||
|
progressView.progress = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
progressView.progress = currentTime / durationTime
|
||||||
|
}
|
||||||
|
|
||||||
|
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||||
|
if keyPath == "isPlaying" {
|
||||||
|
updatePlayerViewStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func updatePlayerViewStatus() {
|
||||||
|
if self.viewModel?.isPlaying == true || !isCurrent {
|
||||||
|
playerImageView.isHidden = true
|
||||||
|
} else {
|
||||||
|
playerImageView.isHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRShortDetailControlView {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
addSubview(progressBgView)
|
||||||
|
progressBgView.addSubview(titleLabel)
|
||||||
|
progressBgView.addSubview(progressView)
|
||||||
|
progressBgView.addSubview(totalTimeLabel)
|
||||||
|
progressBgView.addSubview(currentTimeLabel)
|
||||||
|
addSubview(epButton)
|
||||||
|
addSubview(collectButton)
|
||||||
|
addSubview(playerImageView)
|
||||||
|
|
||||||
|
progressBgView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.bottom.equalToSuperview().offset(-(UIScreen.safeBottom + 5))
|
||||||
|
make.height.equalTo(88)
|
||||||
|
}
|
||||||
|
|
||||||
|
titleLabel.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalTo(progressBgView.snp.top).offset(23)
|
||||||
|
make.left.equalToSuperview().offset(9)
|
||||||
|
make.right.lessThanOrEqualToSuperview().offset(-9)
|
||||||
|
}
|
||||||
|
|
||||||
|
progressView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(4)
|
||||||
|
make.right.equalToSuperview().offset(-6)
|
||||||
|
make.bottom.equalToSuperview().offset(-30)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalTimeLabel.snp.makeConstraints { make in
|
||||||
|
make.right.equalToSuperview().offset(-11)
|
||||||
|
make.bottom.equalToSuperview().offset(-24)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTimeLabel.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(9)
|
||||||
|
make.bottom.equalToSuperview().offset(-24)
|
||||||
|
}
|
||||||
|
|
||||||
|
epButton.snp.makeConstraints { make in
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
make.bottom.equalTo(progressBgView.snp.top).offset(-44)
|
||||||
|
}
|
||||||
|
|
||||||
|
collectButton.snp.makeConstraints { make in
|
||||||
|
make.centerX.equalTo(epButton)
|
||||||
|
make.bottom.equalTo(epButton.snp.top).offset(-25)
|
||||||
|
}
|
||||||
|
|
||||||
|
playerImageView.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
43
SynthReel/Class/Player/V/SRShortDetailPlayerCell.swift
Normal file
43
SynthReel/Class/Player/V/SRShortDetailPlayerCell.swift
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// SRShortDetailPlayerCell.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import JXPlayer
|
||||||
|
|
||||||
|
class SRShortDetailPlayerCell: JXPlayerListCell {
|
||||||
|
|
||||||
|
override var ControlViewClass: JXPlayerListControlView.Type {
|
||||||
|
return SRShortDetailControlView.self
|
||||||
|
}
|
||||||
|
|
||||||
|
var sr_controlView: SRShortDetailControlView {
|
||||||
|
return self.controlView as! SRShortDetailControlView
|
||||||
|
}
|
||||||
|
|
||||||
|
var sr_viewModel: SRShortPlayerViewModel? {
|
||||||
|
return self.viewModel as? SRShortPlayerViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
override var model: Any? {
|
||||||
|
didSet {
|
||||||
|
let model = self.model as? SRVideoInfoModel
|
||||||
|
self.player.setPlayUrl(url: model?.video_url ?? "")
|
||||||
|
|
||||||
|
// self.lockView.isHidden = !(model?.is_lock ?? true)
|
||||||
|
// lockView.videoInfo = model
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortModel: SRShortModel? {
|
||||||
|
didSet {
|
||||||
|
self.sr_controlView.shortModel = shortModel
|
||||||
|
self.player.coverImageView?.sr_setImage(shortModel?.image_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
107
SynthReel/Class/Player/VC/SRDetailPlayerViewController.swift
Normal file
107
SynthReel/Class/Player/VC/SRDetailPlayerViewController.swift
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// SRDetailPlayerViewController.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/17.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import JXPlayer
|
||||||
|
import SnapKit
|
||||||
|
|
||||||
|
class SRDetailPlayerViewController: JXPlayerListViewController {
|
||||||
|
|
||||||
|
var shortId: String? {
|
||||||
|
set {
|
||||||
|
sr_viewModel.shortId = newValue ?? "0"
|
||||||
|
}
|
||||||
|
get {
|
||||||
|
return sr_viewModel.shortId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override var ViewModelClass: JXPlayerListViewModel.Type {
|
||||||
|
return SRShortPlayerViewModel.self
|
||||||
|
}
|
||||||
|
|
||||||
|
var sr_viewModel: SRShortPlayerViewModel {
|
||||||
|
return self.viewModel as! SRShortPlayerViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var returnButton: UIButton = {
|
||||||
|
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.sr_handleNavigationBack()
|
||||||
|
}))
|
||||||
|
button.setImage(UIImage(named: "arrow_left_icon_01"), for: .normal)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
srPrint(message: "销毁")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
self.view.backgroundColor = .black
|
||||||
|
self.register(SRShortDetailPlayerCell.self, forCellWithReuseIdentifier: "SRShortDetailPlayerCell")
|
||||||
|
self.delegate = self
|
||||||
|
self.dataSource = self
|
||||||
|
|
||||||
|
sr_setupUI()
|
||||||
|
|
||||||
|
Task {
|
||||||
|
await self.sr_viewModel.requestShortDetail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func play() {
|
||||||
|
let videoInfo = self.viewModel.currentCell?.model as? SRVideoInfoModel
|
||||||
|
super.play()
|
||||||
|
|
||||||
|
Task {
|
||||||
|
await SRShortApi.requestCreatePlayHistory(shortId: videoInfo?.short_play_id, videoId: videoInfo?.short_play_video_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SRDetailPlayerViewController {
|
||||||
|
|
||||||
|
private func sr_setupUI() {
|
||||||
|
view.addSubview(returnButton)
|
||||||
|
|
||||||
|
returnButton.snp.makeConstraints { make in
|
||||||
|
make.height.equalTo(44)
|
||||||
|
make.width.equalTo(44)
|
||||||
|
make.left.equalToSuperview().offset(10)
|
||||||
|
make.top.equalToSuperview().offset(UIScreen.safeTop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: JXPlayerListViewControllerDelegate JXPlayerListViewControllerDataSource
|
||||||
|
extension SRDetailPlayerViewController: JXPlayerListViewControllerDelegate, JXPlayerListViewControllerDataSource {
|
||||||
|
func jx_playerListViewController(_ viewController: JXPlayerListViewController, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = self.dequeueReusableCell(withReuseIdentifier: "SRShortDetailPlayerCell", for: indexPath) as! SRShortDetailPlayerCell
|
||||||
|
cell.model = self.sr_viewModel.dataArr[indexPath.section].episodeList?[indexPath.row]
|
||||||
|
cell.shortModel = self.sr_viewModel.dataArr[indexPath.section].shortPlayInfo
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func jx_playerListViewController(_ viewController: JXPlayerListViewController, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return self.sr_viewModel.dataArr[section].episodeList?.count ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func jx_numberOfSections(in viewController: JXPlayerListViewController) -> Int {
|
||||||
|
return self.sr_viewModel.dataArr.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func jx_playerListViewController(_ viewController: JXPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath) {
|
||||||
|
if let view = self.sr_viewModel.popView as? SREpSelectorView {
|
||||||
|
view.selectedIndex = indexPath.row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// SRRecommendPlayerViewController.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/20.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import JXPlayer
|
||||||
|
|
||||||
|
class SRRecommendPlayerViewController: JXPlayerListViewController {
|
||||||
|
|
||||||
|
|
||||||
|
override var ViewModelClass: JXPlayerListViewModel.Type {
|
||||||
|
return SRRecommendPlayerViewModel.self
|
||||||
|
}
|
||||||
|
|
||||||
|
override var contentSize: CGSize {
|
||||||
|
return .init(width: UIScreen.width, height: UIScreen.height - UIScreen.tabBarHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sr_viewModel: SRRecommendPlayerViewModel {
|
||||||
|
return self.viewModel as! SRRecommendPlayerViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
self.register(SRRecommendPlayerCell.self, forCellWithReuseIdentifier: "SRRecommendPlayerCell")
|
||||||
|
|
||||||
|
self.delegate = self
|
||||||
|
self.dataSource = self
|
||||||
|
|
||||||
|
Task {
|
||||||
|
await self.sr_viewModel.requestRecommendList(page: 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
self.navigationController?.setNavigationBarHidden(true, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: JXPlayerListViewControllerDelegate JXPlayerListViewControllerDataSource
|
||||||
|
extension SRRecommendPlayerViewController: JXPlayerListViewControllerDelegate, JXPlayerListViewControllerDataSource {
|
||||||
|
func jx_playerListViewController(_ viewController: JXPlayerListViewController, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = self.dequeueReusableCell(withReuseIdentifier: "SRRecommendPlayerCell", for: indexPath) as! SRRecommendPlayerCell
|
||||||
|
cell.model = self.sr_viewModel.dataArr[indexPath.row]
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func jx_playerListViewController(_ viewController: JXPlayerListViewController, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return self.sr_viewModel.dataArr.count
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
54
SynthReel/Class/Player/VM/SRRecommendPlayerViewModel.swift
Normal file
54
SynthReel/Class/Player/VM/SRRecommendPlayerViewModel.swift
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// SRRecommendPlayerViewModel.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/20.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import JXPlayer
|
||||||
|
|
||||||
|
class SRRecommendPlayerViewModel: JXPlayerListViewModel {
|
||||||
|
|
||||||
|
lazy var dataArr: [SRShortModel] = []
|
||||||
|
|
||||||
|
nonisolated required init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private func addDataArr(dataArr: [SRShortModel]) {
|
||||||
|
guard dataArr.count > 0 else { return }
|
||||||
|
|
||||||
|
var indexPaths: [IndexPath] = []
|
||||||
|
var startRow = self.dataArr.count
|
||||||
|
|
||||||
|
dataArr.forEach { _ in
|
||||||
|
indexPaths.append(IndexPath(row: startRow, section: 0))
|
||||||
|
startRow += 1
|
||||||
|
}
|
||||||
|
self.dataArr += dataArr
|
||||||
|
|
||||||
|
CATransaction.setCompletionBlock(nil)
|
||||||
|
CATransaction.begin()
|
||||||
|
self.playerListVC?.collectionView.insertItems(at: indexPaths)
|
||||||
|
CATransaction.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestRecommendList(page: Int) async {
|
||||||
|
guard let dataArr = await SRHomeApi.requestHomeRecommendData(page: page) else { return }
|
||||||
|
|
||||||
|
if page == 1 {
|
||||||
|
self.playerListVC?.clearData()
|
||||||
|
self.dataArr = dataArr
|
||||||
|
self.playerListVC?.reloadData { [weak self] in
|
||||||
|
|
||||||
|
self?.playerListVC?.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.addDataArr(dataArr: dataArr)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
72
SynthReel/Class/Player/VM/SRShortPlayerViewModel.swift
Normal file
72
SynthReel/Class/Player/VM/SRShortPlayerViewModel.swift
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
//
|
||||||
|
// SRShortPlayerViewModel.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/18.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import JXPlayer
|
||||||
|
import HWPanModal
|
||||||
|
|
||||||
|
class SRShortPlayerViewModel: JXPlayerListViewModel {
|
||||||
|
|
||||||
|
var shortId: String = "0"
|
||||||
|
|
||||||
|
var dataArr: [SRShortDetailModel] = []
|
||||||
|
|
||||||
|
weak var popView: UIView?
|
||||||
|
|
||||||
|
nonisolated required init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func requestShortDetail(indexPath: IndexPath? = nil) async -> Int? {
|
||||||
|
let (model, code, _) = await SRShortApi.requestShortDetail(shortId)
|
||||||
|
guard let model = model else { return code }
|
||||||
|
|
||||||
|
self.dataArr.removeAll()
|
||||||
|
self.dataArr.append(model)
|
||||||
|
|
||||||
|
self.playerListVC?.reloadData { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
var targetIndexPath = IndexPath(row: 0, section: 0)
|
||||||
|
|
||||||
|
if let indexPath = indexPath, indexPath.row < (model.episodeList?.count ?? 0) {
|
||||||
|
targetIndexPath = indexPath
|
||||||
|
} else if let videoInfo = model.video_info {
|
||||||
|
var row: Int?
|
||||||
|
model.episodeList?.enumerated().forEach {
|
||||||
|
if $1.short_play_video_id == videoInfo.short_play_video_id {
|
||||||
|
row = $0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let row = row {
|
||||||
|
targetIndexPath = .init(row: row, section: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.playerListVC?.scrollToItem(indexPath: targetIndexPath, animated: false)
|
||||||
|
}
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension SRShortPlayerViewModel {
|
||||||
|
|
||||||
|
func onEpSelectorView() {
|
||||||
|
let view = SREpSelectorView()
|
||||||
|
view.model = self.dataArr[currentIndexPath.section]
|
||||||
|
view.selectedIndex = self.currentIndexPath.row
|
||||||
|
view.didSelected = { [weak self] index in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.playerListVC?.scrollToItem(indexPath: IndexPath(row: index, section: currentIndexPath.section), animated: false)
|
||||||
|
}
|
||||||
|
view.present(in: nil)
|
||||||
|
self.popView = view
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
18
SynthReel/Delegate/AppDelegate+Config.swift
Normal file
18
SynthReel/Delegate/AppDelegate+Config.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// AppDelegate+Config.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension AppDelegate {
|
||||||
|
|
||||||
|
func setConfig() {
|
||||||
|
SRToast.config()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
46
SynthReel/Delegate/AppDelegate.swift
Normal file
46
SynthReel/Delegate/AppDelegate.swift
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
//
|
||||||
|
// AppDelegate.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
SRTool.appDelegate = self
|
||||||
|
|
||||||
|
SRNetworkReachableManager.manager.startMonitoring()
|
||||||
|
|
||||||
|
self.setConfig()
|
||||||
|
|
||||||
|
|
||||||
|
Task {
|
||||||
|
await SRAccountManager.manager.updateUserInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: UISceneSession Lifecycle
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||||
|
// Called when a new scene session is being created.
|
||||||
|
// Use this method to select a configuration to create the new scene with.
|
||||||
|
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
|
||||||
|
// Called when the user discards a scene session.
|
||||||
|
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
|
||||||
|
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
65
SynthReel/Delegate/SceneDelegate.swift
Normal file
65
SynthReel/Delegate/SceneDelegate.swift
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
//
|
||||||
|
// SceneDelegate.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
|
var window: UIWindow?
|
||||||
|
|
||||||
|
|
||||||
|
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||||
|
SRTool.sceneDelegate = self
|
||||||
|
guard let windowScene = (scene as? UIWindowScene) else { return }
|
||||||
|
SRTool.windowScene = windowScene
|
||||||
|
|
||||||
|
window = UIWindow(windowScene: windowScene)
|
||||||
|
|
||||||
|
window?.rootViewController = SRTabBarController()
|
||||||
|
window?.makeKeyAndVisible()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(abcd), name: NSNotification.Name(rawValue: "abcd"), object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func abcd() {
|
||||||
|
window?.rootViewController = SRTabBarController()
|
||||||
|
window?.makeKeyAndVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
func sceneDidDisconnect(_ scene: UIScene) {
|
||||||
|
// Called as the scene is being released by the system.
|
||||||
|
// This occurs shortly after the scene enters the background, or when its session is discarded.
|
||||||
|
// Release any resources associated with this scene that can be re-created the next time the scene connects.
|
||||||
|
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
|
||||||
|
}
|
||||||
|
|
||||||
|
func sceneDidBecomeActive(_ scene: UIScene) {
|
||||||
|
// Called when the scene has moved from an inactive state to an active state.
|
||||||
|
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
|
||||||
|
}
|
||||||
|
|
||||||
|
func sceneWillResignActive(_ scene: UIScene) {
|
||||||
|
// Called when the scene will move from an active state to an inactive state.
|
||||||
|
// This may occur due to temporary interruptions (ex. an incoming phone call).
|
||||||
|
}
|
||||||
|
|
||||||
|
func sceneWillEnterForeground(_ scene: UIScene) {
|
||||||
|
// Called as the scene transitions from the background to the foreground.
|
||||||
|
// Use this method to undo the changes made on entering the background.
|
||||||
|
}
|
||||||
|
|
||||||
|
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||||
|
// Called as the scene transitions from the foreground to the background.
|
||||||
|
// Use this method to save data, release shared resources, and store enough scene-specific state information
|
||||||
|
// to restore the scene back to its current state.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
49
SynthReel/Libs/SRAccount/SRAccountManager.swift
Normal file
49
SynthReel/Libs/SRAccount/SRAccountManager.swift
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// SRAccountManager.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRAccountManager: NSObject {
|
||||||
|
|
||||||
|
|
||||||
|
static let manager = SRAccountManager()
|
||||||
|
|
||||||
|
private(set) var token = UserDefaults.sr_object(forKey: kSRAccountTokenDefaultsKey, as: SRAccountToken.self)
|
||||||
|
private(set) var userInfo = UserDefaults.sr_object(forKey: kSRUserInfoDefaultsKey, as: SRUserInfo.self)
|
||||||
|
|
||||||
|
|
||||||
|
func setAccountToken(_ token: SRAccountToken?) {
|
||||||
|
self.token = token
|
||||||
|
UserDefaults.sr_setObject(token, forKey: kSRAccountTokenDefaultsKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///更新用户信息
|
||||||
|
func updateUserInfo() async {
|
||||||
|
// Task {
|
||||||
|
// completer?()
|
||||||
|
// }
|
||||||
|
if let userInfo = await SRUserApi.requestUserInfo() {
|
||||||
|
self.userInfo = userInfo
|
||||||
|
UserDefaults.sr_setObject(userInfo, forKey: kSRUserInfoDefaultsKey)
|
||||||
|
NotificationCenter.default.post(name: SRAccountManager.userInfoUpdateNotification, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension SRAccountManager {
|
||||||
|
|
||||||
|
///用户信息更新
|
||||||
|
@objc static let userInfoUpdateNotification = NSNotification.Name(rawValue: "SRAccountManager.userInfoUpdateNotification")
|
||||||
|
///登录状态发生变化
|
||||||
|
@objc static let loginStatusChangeNotification = NSNotification.Name(rawValue: "SRAccountManager.loginStatusChangeNotification")
|
||||||
|
}
|
||||||
42
SynthReel/Libs/SRAccount/SRAccountToken.swift
Normal file
42
SynthReel/Libs/SRAccount/SRAccountToken.swift
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// SRAccountToken.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SmartCodable
|
||||||
|
|
||||||
|
|
||||||
|
class SRAccountToken: NSObject, SmartCodable, NSSecureCoding {
|
||||||
|
|
||||||
|
required override init() { }
|
||||||
|
|
||||||
|
var auto_login: Int?
|
||||||
|
var token: String?
|
||||||
|
var customer_id: String?
|
||||||
|
|
||||||
|
|
||||||
|
static var supportsSecureCoding: Bool {
|
||||||
|
get {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(with coder: NSCoder) {
|
||||||
|
coder.encode(token, forKey: "token")
|
||||||
|
coder.encode(customer_id, forKey: "customer_id")
|
||||||
|
coder.encode(auto_login, forKey: "auto_login")
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
token = coder.decodeObject(of: NSString.self, forKey: "token") as? String
|
||||||
|
customer_id = coder.decodeObject(of: NSString.self, forKey: "customer_id") as? String
|
||||||
|
auto_login = coder.decodeObject(of: NSNumber.self, forKey: "auto_login")?.intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
66
SynthReel/Libs/SRAccount/SRUserInfo.swift
Normal file
66
SynthReel/Libs/SRAccount/SRUserInfo.swift
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
//
|
||||||
|
// SRUserInfo.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SmartCodable
|
||||||
|
|
||||||
|
class SRUserInfo: NSObject, SmartCodable, NSSecureCoding {
|
||||||
|
|
||||||
|
required override init() { }
|
||||||
|
|
||||||
|
var id: String?
|
||||||
|
var avator: String?
|
||||||
|
var coin_left_total: Int?
|
||||||
|
var family_name: String?
|
||||||
|
var send_coin_left_total: Int?
|
||||||
|
var vip_end_time: TimeInterval?
|
||||||
|
var is_vip: Bool?
|
||||||
|
var customer_id: String?
|
||||||
|
var is_tourist: Bool?
|
||||||
|
|
||||||
|
func getNickName() -> String {
|
||||||
|
if let name = family_name, !name.isEmpty {
|
||||||
|
return name
|
||||||
|
} else {
|
||||||
|
return "Visitor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCoins: Int {
|
||||||
|
return (coin_left_total ?? 0) + (send_coin_left_total ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var supportsSecureCoding: Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(with coder: NSCoder) {
|
||||||
|
coder.encode(id, forKey: "id")
|
||||||
|
coder.encode(customer_id, forKey: "customer_id")
|
||||||
|
coder.encode(is_tourist, forKey: "is_tourist")
|
||||||
|
coder.encode(avator, forKey: "avator")
|
||||||
|
coder.encode(family_name, forKey: "family_name")
|
||||||
|
coder.encode(coin_left_total, forKey: "coin_left_total")
|
||||||
|
coder.encode(send_coin_left_total, forKey: "send_coin_left_total")
|
||||||
|
coder.encode(is_vip, forKey: "is_vip")
|
||||||
|
coder.encode(vip_end_time, forKey: "vip_end_time")
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init()
|
||||||
|
id = coder.decodeObject(of: NSString.self, forKey: "id") as? String
|
||||||
|
customer_id = coder.decodeObject(of: NSString.self, forKey: "customer_id") as? String
|
||||||
|
is_tourist = coder.decodeObject(of: NSNumber.self, forKey: "is_tourist")?.boolValue
|
||||||
|
avator = coder.decodeObject(of: NSString.self, forKey: "avator") as? String
|
||||||
|
family_name = coder.decodeObject(of: NSString.self, forKey: "family_name") as? String
|
||||||
|
coin_left_total = coder.decodeObject(of: NSNumber.self, forKey: "coin_left_total")?.intValue
|
||||||
|
send_coin_left_total = coder.decodeObject(of: NSNumber.self, forKey: "send_coin_left_total")?.intValue
|
||||||
|
is_vip = coder.decodeObject(of: NSNumber.self, forKey: "is_vip")?.boolValue
|
||||||
|
vip_end_time = coder.decodeObject(of: NSNumber.self, forKey: "vip_end_time")?.doubleValue
|
||||||
|
}
|
||||||
|
}
|
||||||
27
SynthReel/Libs/SRDeviceID/SRDeviceId.swift
Normal file
27
SynthReel/Libs/SRDeviceID/SRDeviceId.swift
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// SRDeviceId.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRDeviceId {
|
||||||
|
|
||||||
|
static let shared = SRDeviceId()
|
||||||
|
private let key = "com.synthreel.deviceid"
|
||||||
|
|
||||||
|
// private init() {}
|
||||||
|
|
||||||
|
lazy var id: String = {
|
||||||
|
if let savedID = SRKeychain.shared.read(key: key) {
|
||||||
|
return savedID
|
||||||
|
} else {
|
||||||
|
let newID = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString
|
||||||
|
SRKeychain.shared.save(key: key, value: newID)
|
||||||
|
return newID
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
59
SynthReel/Libs/SRDeviceID/SRKeychain.swift
Normal file
59
SynthReel/Libs/SRDeviceID/SRKeychain.swift
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
//
|
||||||
|
// SRKeychain.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/12.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRKeychain {
|
||||||
|
static let shared = SRKeychain()
|
||||||
|
|
||||||
|
func save(key: String, value: String) {
|
||||||
|
if let data = value.data(using: .utf8) {
|
||||||
|
// 先删除旧的
|
||||||
|
let query = [
|
||||||
|
kSecClass: kSecClassGenericPassword,
|
||||||
|
kSecAttrAccount: key
|
||||||
|
] as CFDictionary
|
||||||
|
SecItemDelete(query)
|
||||||
|
|
||||||
|
// 再保存新的
|
||||||
|
let attributes = [
|
||||||
|
kSecClass: kSecClassGenericPassword,
|
||||||
|
kSecAttrAccount: key,
|
||||||
|
kSecValueData: data,
|
||||||
|
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock
|
||||||
|
] as CFDictionary
|
||||||
|
|
||||||
|
SecItemAdd(attributes, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func read(key: String) -> String? {
|
||||||
|
let query = [
|
||||||
|
kSecClass: kSecClassGenericPassword,
|
||||||
|
kSecAttrAccount: key,
|
||||||
|
kSecReturnData: kCFBooleanTrue!,
|
||||||
|
kSecMatchLimit: kSecMatchLimitOne
|
||||||
|
] as CFDictionary
|
||||||
|
|
||||||
|
var dataTypeRef: AnyObject?
|
||||||
|
let status = SecItemCopyMatching(query, &dataTypeRef)
|
||||||
|
|
||||||
|
if status == errSecSuccess, let data = dataTypeRef as? Data {
|
||||||
|
return String(data: data, encoding: .utf8)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func delete(key: String) {
|
||||||
|
let query = [
|
||||||
|
kSecClass: kSecClassGenericPassword,
|
||||||
|
kSecAttrAccount: key
|
||||||
|
] as CFDictionary
|
||||||
|
SecItemDelete(query)
|
||||||
|
}
|
||||||
|
}
|
||||||
22
SynthReel/Libs/SRHud/SRHud.swift
Normal file
22
SynthReel/Libs/SRHud/SRHud.swift
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// SRHud.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SVProgressHUD
|
||||||
|
|
||||||
|
struct SRHud {
|
||||||
|
|
||||||
|
static func show(containerView: UIView? = nil, type: SVProgressHUDMaskType = .clear) {
|
||||||
|
SVProgressHUD.setContainerView(containerView)
|
||||||
|
SVProgressHUD.setDefaultMaskType(type)
|
||||||
|
SVProgressHUD.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
static func dismiss() {
|
||||||
|
SVProgressHUD.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
24
SynthReel/Libs/SRHud/SRToast.swift
Normal file
24
SynthReel/Libs/SRHud/SRToast.swift
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// SRToast.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import Toast
|
||||||
|
|
||||||
|
struct SRToast {
|
||||||
|
|
||||||
|
static func config() {
|
||||||
|
CSToastManager.setTapToDismissEnabled(false)
|
||||||
|
CSToastManager.setDefaultDuration(2)
|
||||||
|
CSToastManager.setDefaultPosition(CSToastPositionCenter)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func show(text: String?) {
|
||||||
|
guard let text = text else { return }
|
||||||
|
SRTool.keyWindow?.makeToast(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
56
SynthReel/Libs/Tool/SRTool.swift
Normal file
56
SynthReel/Libs/Tool/SRTool.swift
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// SRTool.swift
|
||||||
|
// SynthReel
|
||||||
|
//
|
||||||
|
// Created by 湖北秦九 on 2025/11/13.
|
||||||
|
// Copyright © 2025 SR. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SRTool {
|
||||||
|
|
||||||
|
static var appDelegate: AppDelegate?
|
||||||
|
static var sceneDelegate: SceneDelegate?
|
||||||
|
|
||||||
|
static var windowScene: UIWindowScene?
|
||||||
|
|
||||||
|
|
||||||
|
static var keyWindow: UIWindow? {
|
||||||
|
return windowScene?.keyWindow
|
||||||
|
}
|
||||||
|
|
||||||
|
static var rootViewController: UIViewController? {
|
||||||
|
return keyWindow?.rootViewController
|
||||||
|
}
|
||||||
|
|
||||||
|
///获得启动图
|
||||||
|
static var lanuchViewController: UIViewController? {
|
||||||
|
let storyboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
|
||||||
|
let vc = storyboard.instantiateInitialViewController()
|
||||||
|
return vc
|
||||||
|
}
|
||||||
|
|
||||||
|
static var topViewController: UIViewController? {
|
||||||
|
var resultVC: UIViewController? = self.rootViewController
|
||||||
|
if let rootNav = resultVC as? UINavigationController {
|
||||||
|
resultVC = rootNav.topViewController
|
||||||
|
}
|
||||||
|
|
||||||
|
resultVC = self._topViewController(resultVC)
|
||||||
|
while resultVC?.presentedViewController != nil {
|
||||||
|
resultVC = self._topViewController(resultVC?.presentedViewController)
|
||||||
|
}
|
||||||
|
return resultVC
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func _topViewController(_ vc: UIViewController?) -> UIViewController? {
|
||||||
|
if vc is UINavigationController {
|
||||||
|
return _topViewController((vc as? UINavigationController)?.topViewController)
|
||||||
|
} else if vc is UITabBarController {
|
||||||
|
return _topViewController((vc as? UITabBarController)?.selectedViewController)
|
||||||
|
} else {
|
||||||
|
return vc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "tinted"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x01",
|
||||||
|
"green" : "0x01",
|
||||||
|
"red" : "0x01"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x22",
|
||||||
|
"green" : "0x1B",
|
||||||
|
"red" : "0x05"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user