Compare commits

...

11 Commits
main ... IAA

Author SHA1 Message Date
澜声世纪
67eeef0447 1.开屏广告 2025-12-11 11:17:46 +08:00
澜声世纪
4c250038e0 1.w2a 启动页 通知 2025-12-10 10:23:22 +08:00
澜声世纪
b7a0a86a1a 1.文件目录修改 2025-12-08 17:32:39 +08:00
澜声世纪
41b7bc80c4 1.vip挽留 2025-12-08 17:06:30 +08:00
澜声世纪
50d1cd9069 1.ui调整 2025-12-08 16:04:26 +08:00
澜声世纪
2c7cef9572 1.混淆代码 2025-12-08 13:48:40 +08:00
HiBit
6d48234e48 1.代码提交 2025-12-08 10:34:34 +08:00
HiBit
5fb7a4d9bc 登陆功能 2025-12-02 13:27:27 +08:00
HiBit
2b3125d826 1.代码提交 2025-11-29 14:05:30 +08:00
HiBit
b21751e738 web view 2025-11-28 15:03:10 +08:00
HiBit
86b603c64f 1.缺省页 UI 2025-11-26 16:55:40 +08:00
795 changed files with 56698 additions and 413 deletions

10
Podfile
View File

@ -20,7 +20,7 @@ target 'SynthReel' do
pod 'YYCategories'
pod 'YYText'
pod 'Kingfisher'
pod 'SmartCodable'
pod 'SmartCodable','5.0.15'
pod 'Moya'
pod 'SVProgressHUD'
pod 'Toast'
@ -31,5 +31,13 @@ target 'SynthReel' do
pod 'MJRefresh'
pod 'collection-view-layouts/TagsLayout'
pod 'HWPanModal'
pod 'LYEmptyView'
pod 'ZLPhotoBrowser'
pod 'Adjust'
# # AdMob SDK
pod 'Google-Mobile-Ads-SDK'
#
# # AppLovin SDK
pod 'AppLovinSDK'
end

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,24 @@
{
"originHash" : "0e62262c59a183f44748a161870cc0f2b76e1b0e46f648559704e4be9de523b9",
"originHash" : "deb3ea9bc9f7ea16d149d6025086ec795e4f5fc740540ae9e5e8308e4e3e8aec",
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024072200.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
"identity" : "estabbarcontroller",
"kind" : "remoteSourceControl",
@ -10,6 +28,114 @@
"version" : "2.9.0"
}
},
{
"identity" : "facebook-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/facebook/facebook-ios-sdk",
"state" : {
"revision" : "c19607d535864533523d1f437c84035e5fb101cf",
"version" : "14.1.0"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "087bb95235f676c1a37e928769a5b6645dcbd325",
"version" : "12.6.0"
}
},
{
"identity" : "google-ads-on-device-conversion-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
"state" : {
"revision" : "35b601a60fbbea2de3ea461f604deaaa4d8bbd0c",
"version" : "3.2.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "c2d59acf17a8ba7ed80a763593c67c9c7c006ad1",
"version" : "12.5.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "60da361632d0de02786f709bdc0c4df340f7613e",
"version" : "8.1.0"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6",
"version" : "1.69.1"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "fb7f2740b1570d2f7599c6bb9531bf4fad6974b7",
"version" : "5.0.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "snapkit",
"kind" : "remoteSourceControl",
@ -18,6 +144,15 @@
"revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4",
"version" : "5.7.1"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "c169a5744230951031770e27e475ff6eefe51f9d",
"version" : "1.33.3"
}
}
],
"version" : 3

View File

@ -2,7 +2,7 @@
// SRHomeApi.swift
// SynthReel
//
// Created by on 2025/11/14.
// Created by on 2025/11/14.
// Copyright © 2025 SR. All rights reserved.
//
@ -118,5 +118,4 @@ struct SRHomeApi {
}
}
}
}

View File

@ -2,7 +2,7 @@
// SRShortApi.swift
// SynthReel
//
// Created by on 2025/11/18.
// Created by on 2025/11/18.
// Copyright © 2025 SR. All rights reserved.
//
@ -29,6 +29,20 @@ struct SRShortApi {
}
}
static func requestRecommand() async -> ([SRShortModel]?, Int?, String?) {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/getDetailsRecommand")
param.method = .get
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRNetwork.List<SRShortModel>>) in
if response.isSuccess {
continuation.resume(returning:(response.data?.list, 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 = ""
@ -75,6 +89,24 @@ struct SRShortApi {
}
}
static func requestCoinUnlockVideo(shortId: String?, videoId: String?) async -> SRVideoUnlockResult? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/buy_video")
param.isToast = true
param.method = .post
param.parameters = [
"short_play_id": shortId ?? "",
"video_id": videoId ?? "0"
]
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRVideoUnlockResult>) in
continuation.resume(returning: response.data)
}
}
}
}

View File

@ -0,0 +1,98 @@
//
// SRStatAPI.swift
// SynthReel
//
// Created by on 2025/12/2.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRStatAPI: NSObject {
enum EventKey: String {
case payError = "pay_error" //
case payCallback = "pay_callback" //
case payRestore = "pay_restore" //restore
case forceUpdate = "force_update"//
case payCancel = "pay_cancel" //
case payTemplateDialog = "pay_template_dialog"//
}
static func requestEventStat(orderCode: String?, shortPlayId: String?, videoId: String?, eventKey: EventKey, errorMsg: String?, otherParamenters: [String : Any]? = nil) async {
var eventName = ""
switch eventKey {
case .payRestore:
eventName = "pay restore"
case .payCallback:
eventName = "pay callback failed"
case .forceUpdate:
eventName = "force update"
case .payCancel:
eventName = "user pay canceled"
default:
eventName = "platform pay failed"
}
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/createOrder")
param.method = .post
param.parameters = [
"userId" : SRLogin.manager.userInfo?.customer_id ?? "",
"short_play_video_id" : videoId ?? "0",
"short_play_id" : shortPlayId ?? "0",
"event_key" : eventKey.rawValue,
"order_code": orderCode ?? "",
"event_name" : eventName,
]
if let errorMsg = errorMsg {
param.parameters?["error_msg"] = errorMsg
}
if let otherParamenters = otherParamenters {
otherParamenters.forEach {
param.parameters?[$0] = $1
}
}
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestUploadApnsAuthorizationStatus(_ status: Bool) async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/uploadNoticeStatus")
param.method = .post
param.parameters = [
"is_open_notice" : status ? 1 : 0
]
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
}
extension SRStatAPI {
///
static func uploadApnsAuthorizationStatus() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
if settings.authorizationStatus == .authorized {
Task {
await requestUploadApnsAuthorizationStatus(true)
}
} else if settings.authorizationStatus == .denied {
Task {
await requestUploadApnsAuthorizationStatus(false)
}
}
}
}
}

View File

@ -0,0 +1,121 @@
//
// SRStoreAPI.swift
// SynthReel
//
// Created by on 2025/12/2.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
class SRStoreAPI: NSObject {
enum BuyType: String, SmartCaseDefaultable {
case coins = "coins"
case subVip = "sub_vip"
case subCoins = "sub_coins"
case vip = "vip"
}
static func requestPayTemplate() async -> SRPayDateModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/paySettingsV4")
param.method = .post
param.parameters = [
"discount" : "1",
"purchases_token" : SRPayManger.manager.getAppStoreReceipt() ?? "",
]
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRPayDateModel>) in
continuation.resume(returning: response.data)
}
}
}
static func requestCreateOrder(payId: String, shortPlayId: String, videoId: String, isDiscount: Bool = false, identifierDiscount: String? = nil) async -> SRIapOrderModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/createOrder")
param.method = .post
param.parameters = [
"payment_channel" : "apple",
"short_play_id" : shortPlayId,
"video_id" : videoId,
"pay_setting_id" : payId,
"is_discount" : isDiscount ? 1 : 0,
"product_discount" : identifierDiscount ?? "",
]
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRIapOrderModel>) in
continuation.resume(returning: response.data)
}
}
}
static func requestVerifyOrder(parameters: [String : Any]) async -> SRIapVerifyModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/applePaid")
param.method = .post
param.parameters = parameters
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRIapVerifyModel>) in
continuation.resume(returning: response.data)
}
}
}
static func requestCoinsPackData() async -> SRCoinsPackModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/getReceiveDayCoinInfo")
param.method = .get
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRCoinsPackModel>) in
continuation.resume(returning: response.data)
}
}
}
static func requestCoinBagCanReceiveInfo() async -> SRCoinPackCanReceiveModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/getReceiveDayCoin")
param.method = .get
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRCoinPackCanReceiveModel>) in
continuation.resume(returning: response.data)
}
}
}
static func requestReceiveCoinsPackCoins(id: String?) async -> Bool {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/receiveDayCoin")
param.method = .post
param.parameters = [
"id" : id ?? ""
]
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<String>) in
if response.isSuccess {
continuation.resume(returning: true)
}else{
continuation.resume(returning: false)
}
}
}
}
static func requestVipRetainPayInfo() async -> SRPayAlertModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/getRetainVipPaySetting")
param.method = .get
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRPayAlertModel>) in
if let model = response.data,
model.info != nil {
continuation.resume(returning: model)
} else {
continuation.resume(returning: nil)
}
}
}
}
}

View File

@ -2,7 +2,7 @@
// SRUserApi.swift
// SynthReel
//
// Created by on 2025/11/13.
// Created by on 2025/11/13.
// Copyright © 2025 SR. All rights reserved.
//
@ -22,4 +22,119 @@ struct SRUserApi {
}
}
}
static func requestregister() async -> SRTokenModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/register")
param.method = .get
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRTokenModel>) in
continuation.resume(returning: response.data)
}
}
}
static func requestLeave() async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/leaveApp")
param.method = .post
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestEnterApp() async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/enterTheApp")
param.method = .post
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestStatOnLine() async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/onLine")
param.method = .post
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestStatApnsClick(id: String, title: String) async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/message/sendReport")
param.method = .post
param.parameters = [
"message_id" : id,
"title" : title
]
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestUploadApnsDeviceToken(token: String) async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/firebaseToken")
param.method = .post
param.parameters = [
"fcm_token": token
]
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestSignThirdLogin(model: SRThirdModel) async -> SRTokenModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/login")
param.method = .post
param.parameters = model.toDictionary()
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRTokenModel>) in
continuation.resume(returning: response.data)
}
}
}
static func requestLogout() async -> SRTokenModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/signout")
param.method = .post
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRTokenModel>) in
continuation.resume(returning: response.data)
}
}
}
static func requestUploadW2a(data: String) async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/w2aSelfAttribution")
param.method = .post
param.parameters = [
"data": data
]
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestVersionUpdateData() async -> SRVersionUpdateModel?{
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/versionControl")
param.method = .get
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRVersionUpdateModel>) in
continuation.resume(returning: response.data)
}
}
}
}

View File

@ -0,0 +1,225 @@
////
//// VPAppOpenAdManager.swift
//// Veloria
////
//// Created by on 2025/7/15.
////
//
//import UIKit
//
//
//@objc protocol VPAppOpenAdManagerDelegate: NSObjectProtocol {
//
// ///广
// @objc optional func appOpenAdManagerDidLoadFinish(adManager: VPAppOpenAdManager)
// ///广
// @objc optional func appOpenAdManager(adManager: VPAppOpenAdManager, didLoadFail error: Error)
// ///广
// @objc optional func appOpenAdManager(adManager: VPAppOpenAdManager, didDisplayFail error: Error)
// ///广
// @objc optional func appOpenAdManagerDidShow(adManager: VPAppOpenAdManager)
// ///广
// @objc optional func appOpenAdManagerDidDismiss(adManager: VPAppOpenAdManager)
// ///广
// @objc optional func appOpenAdManagerDidClick(adManager: VPAppOpenAdManager)
// ///
// @objc optional func appOpenAdManager(adManager: VPAppOpenAdManager, didOtherFail error: Error)
//
//}
//
//
//@objc protocol VPAppOpenAdDelegate: NSObjectProtocol {
//
// ///广
// @objc optional func appOpenAd(ad: VPAppOpenAd, didLoadFail error: Error)
// ///广
// @objc optional func appOpenAdDidLoadFinish(ad: VPAppOpenAd)
// ///广
// @objc optional func appOpenAd(ad: VPAppOpenAd, didDisplayFail error: Error)
// ///广
// @objc optional func appOpenAdDidShow(ad: VPAppOpenAd)
// ///广
// @objc optional func appOpenAdDidDismiss(ad: VPAppOpenAd)
// ///广
// @objc optional func appOpenAdDidClick(ad: VPAppOpenAd)
//}
//
//@objc protocol VPAppOpenAd: NSObjectProtocol {
//
// weak var delegate: VPAppOpenAdDelegate? { get set }
//
// var adPlatformKey: String { get }
//
// var adUnitID: String { get }
//
// var isReady: Bool { get }
//
// func loadAd()
// func showAd()
//
//}
//
//class VPAppOpenAdManager: NSObject {
//
// static let manager = VPAppOpenAdManager()
//
// weak var delegate: VPAppOpenAdManagerDelegate?
//
// private(set) var appOpenAd: VPAppOpenAd? {
// didSet {
// oldValue?.delegate = nil
// appOpenAd?.delegate = self
// }
// }
//
// private var isLoading = false
// private var isShowing = false
// ///
// private var needAutoShow = false
//
// private var timeOutTimer: Timer?
//
// private var adsDate: Date?
//
// deinit {
// NotificationCenter.default.removeObserver(self)
// }
//
// override init() {
// super.init()
// NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil)
// }
//
// func showAdIfAvailable() {
//
// if self.appOpenAd?.isReady == true {
// self.showAd()
// } else {
// self.timeOutTimer = Timer.scheduledTimer(timeInterval: 5, target: (self), selector: #selector(handleTimeOutTimer), userInfo: nil, repeats: false)
//
// self.needAutoShow = true
// self.loadAd()
// }
// }
//
// func loadAd() {
// guard !isLoading else { return }
//
// self.isLoading = true
// appOpenAd = VPApplovinAppOpenAd()
// appOpenAd?.loadAd()
// }
//
// func showAd() {
// guard self.appOpenAd?.isReady == true else { return }
// guard !self.isShowing else { return }
//
//
// self.isShowing = true
// self.appOpenAd?.showAd()
// }
//
// @objc private func handleTimeOutTimer() {
// self.needAutoShow = false
// cleanTimer()
//
// let error = NSError(domain: "time-out", code: -1)
// requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
// self.delegate?.appOpenAdManager?(adManager: self, didOtherFail: error)
// }
//
// private func cleanTimer() {
// self.timeOutTimer?.invalidate()
// self.timeOutTimer = nil
// }
//
// private func clean() {
// appOpenAd = nil
// delegate = nil
// isShowing = false
// self.adsDate = nil
// }
//}
//
////MARK: -------------- VPAppOpenAdDelegate --------------
//extension VPAppOpenAdManager: VPAppOpenAdDelegate {
//
// ///广
// func appOpenAd(ad: VPAppOpenAd, didLoadFail error: Error) {
// cleanTimer()
// isLoading = false
// requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
//
// self.delegate?.appOpenAdManager?(adManager: self, didLoadFail: error)
//
// clean()
// }
// ///广
// func appOpenAdDidLoadFinish(ad: VPAppOpenAd) {
// cleanTimer()
// self.isLoading = false
// self.delegate?.appOpenAdManagerDidLoadFinish?(adManager: self)
//
// if needAutoShow {
// self.needAutoShow = false
// self.showAd()
// }
//
// }
// ///广
// func appOpenAd(ad: VPAppOpenAd, didDisplayFail error: Error) {
// requestStatAd(type: "show_failed", errorMsg: error.localizedDescription)
// self.delegate?.appOpenAdManager?(adManager: self, didDisplayFail: error)
// clean()
// }
// ///广
// func appOpenAdDidShow(ad: VPAppOpenAd) {
// adsDate = Date()
// requestStatAd(type: "start", errorMsg: nil)
// self.delegate?.appOpenAdManagerDidShow?(adManager: self)
// }
// ///广
// func appOpenAdDidDismiss(ad: VPAppOpenAd) {
// requestStatAd(type: "close", errorMsg: nil)
// self.delegate?.appOpenAdManagerDidDismiss?(adManager: self)
// clean()
// }
// ///广
// func appOpenAdDidClick(ad: VPAppOpenAd) {
// requestStatAd(type: "click", errorMsg: nil)
// self.delegate?.appOpenAdManagerDidClick?(adManager: self)
// }
//}
//
//
//extension VPAppOpenAdManager {
//
// private func requestStatAd(type: String, errorMsg: String?) {
// guard let appOpenAd = self.appOpenAd else { return }
//
// var seconds = 0
// if let adsDate = self.adsDate {
// if type == "click" || type == "close" {
// seconds = Int(Date().timeIntervalSince(adsDate))
// }
// }
//
//
// let model = VPStatAdModel()
// model.type = type
// model.view_seconds = seconds
// model.ads_id = appOpenAd.adUnitID
// model.ad_platform_key = VPAdPlatformKey(rawValue: appOpenAd.adPlatformKey)
// model.error_msg = errorMsg
// model.scene = .splash
//
//// VPStatAPI.requestStatAd(model: model)
// }
//
//
// @objc private func didEnterBackgroundNotification() {
// guard self.isShowing else { return }
//
// self.requestStatAd(type: "Interrupt", errorMsg: nil)
// }
//}

View File

@ -0,0 +1,84 @@
////
//// VPApplovinAppOpenAd.swift
//// Veloria
////
//// Created by on 2025/7/15.
////
//
//import UIKit
//#if canImport(AppLovinSDK)
//import AppLovinSDK
//#endif
//
//class VPApplovinAppOpenAd: NSObject, VPAppOpenAd {
//
//
//
// private lazy var appOpenAd = MAAppOpenAd(adUnitIdentifier: adUnitID)
//
// weak var delegate: (any VPAppOpenAdDelegate)?
//
// var adPlatformKey: String {
// return VPAdPlatformKey.applovin.rawValue
// }
//
// var adUnitID: String {
// return VPAdManager.applovin_appOpenAdUnitID
// }
//
// var isReady: Bool {
// if ALSdk.shared().isInitialized && appOpenAd.isReady {
// return true
// } else {
// return false
// }
// }
//
// func loadAd() {
//
// VPAdManager.initialize_applovinSdk { [weak self] in
// guard let self = self else { return }
// appOpenAd.delegate = self
// appOpenAd.load()
// }
// }
//
// func showAd() {
// guard isReady else { return }
//
// appOpenAd.show()
// }
//}
//
//
//extension VPApplovinAppOpenAd: MAAdDelegate {
// func didLoad(_ ad: MAAd) {
// self.delegate?.appOpenAdDidLoadFinish?(ad: self)
// }
//
// func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
// let nsError = NSError(domain: error.message, code: error.code.rawValue)
// self.delegate?.appOpenAd?(ad: self, didLoadFail: nsError)
// }
//
// func didDisplay(_ ad: MAAd) {
// self.delegate?.appOpenAdDidShow?(ad: self)
// }
//
// func didHide(_ ad: MAAd) {
// self.delegate?.appOpenAdDidDismiss?(ad: self)
// }
//
// func didClick(_ ad: MAAd) {
// self.delegate?.appOpenAdDidClick?(ad: self)
// }
//
// func didFail(toDisplay ad: MAAd, withError error: MAError) {
// let nsError = NSError(domain: error.message, code: error.code.rawValue)
// self.delegate?.appOpenAd?(ad: self, didDisplayFail: nsError)
// }
//
//
//
//}
//

View File

@ -0,0 +1,88 @@
////
//// VPApplovinBannerAd.swift
//// Veloria
////
//// Created by on 2025/7/17.
////
//
//import UIKit
//import AppLovinSDK
//
//class VPApplovinBannerAd: NSObject, VPBannerAd {
//
// let size = CGSize.init(width: UIScreen.width, height: 59)
//
// var delegate: (any VPBannerAdDelegate)?
//
// ///
// private var isLoaded = false
//
// private(set) lazy var _adView: MAAdView = {
// let view = MAAdView(adUnitIdentifier: adUnitID)
// view.frame = .init(x: 0, y: 0, width: size.width, height: size.height)
// view.delegate = self
// return view
// }()
//
// var adView: UIView {
// return _adView
// }
//
// var adPlatformKey: String {
// return VPAdPlatformKey.applovin.rawValue
// }
//
// var adUnitID: String {
// return VPAdManager.applovin_bannerAdUnitID
// }
//
// func loadAd() {
// _adView.loadAd()
// }
//}
//
////MARK: -------------- MAAdViewAdDelegate --------------
//extension VPApplovinBannerAd: MAAdViewAdDelegate {
// func didExpand(_ ad: MAAd) {
//
// }
//
// func didCollapse(_ ad: MAAd) {
//
// }
//
// func didLoad(_ ad: MAAd) {
// if !isLoaded {
// isLoaded = true
// self.delegate?.bannerAdDidLoadFinish?(bannerAd: self)
// }
// }
//
// func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
// if !isLoaded {
// isLoaded = true
// let nsError = NSError(domain: error.message, code: error.code.rawValue)
// self.delegate?.bannerAd?(bannerAd: self, didLoadFail: nsError)
// }
// }
//
// func didDisplay(_ ad: MAAd) {
// self.delegate?.bannerAdDidShow?(bannerAd: self)
// }
//
// func didHide(_ ad: MAAd) {
// self.delegate?.bannerAdDidDismiss?(bannerAd: self)
// }
//
// func didClick(_ ad: MAAd) {
// self.delegate?.bannerAdDidClick?(ad: self)
// }
//
// func didFail(toDisplay ad: MAAd, withError error: MAError) {
//
// }
//
//
//
//}
//

View File

@ -0,0 +1,131 @@
////
//// VPBannerAdManager.swift
//// Veloria
////
//// Created by on 2025/7/15.
////
//
//import UIKit
//#if canImport(AppLovinSDK)
//import AppLovinSDK
//#endif
//
//
//@objc protocol VPBannerAdDelegate: NSObjectProtocol {
// ///广
// @objc optional func bannerAd(bannerAd: VPBannerAd, didLoadFail error: Error)
// ///广
// @objc optional func bannerAdDidLoadFinish(bannerAd: VPBannerAd)
// ///广
// @objc optional func bannerAdDidShow(bannerAd: VPBannerAd)
// ///广
// @objc optional func bannerAdDidDismiss(bannerAd: VPBannerAd)
// ///广
// @objc optional func bannerAdDidClick(ad: VPBannerAd)
//}
//
//@objc protocol VPBannerAd: NSObjectProtocol {
//
// weak var delegate: VPBannerAdDelegate? { get set }
//
// var adView: UIView { get }
//
// var adPlatformKey: String { get }
//
// var adUnitID: String { get }
//
// func loadAd()
//
//}
//
//class VPBannerAdManager: NSObject {
//
// let size = CGSize.init(width: UIScreen.width, height: 59)
//
// var videoInfo: VPVideoInfoModel?
//
// private var adsDate: Date?
//
// private(set) lazy var bannerAd: VPBannerAd = {
// let ad = VPApplovinBannerAd()
// ad.delegate = self
// return ad
// }()
//
// var adView: UIView {
// return bannerAd.adView
// }
//
// deinit {
// NotificationCenter.default.removeObserver(self)
// }
//
// override init() {
// super.init()
// NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil)
//
// bannerAd.loadAd()
// }
//
//
//}
//
////MARK: -------------- VPBannerAdDelegate --------------
//extension VPBannerAdManager: VPBannerAdDelegate {
//
// ///广
// func bannerAd(bannerAd: VPBannerAd, didLoadFail error: Error) {
// requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
// }
// ///广
// func bannerAdDidLoadFinish(bannerAd: VPBannerAd) {
// adsDate = Date()
// requestStatAd(type: "start", errorMsg: nil)
// }
// ///广
// func bannerAdDidShow(bannerAd: VPBannerAd) {
//
// }
// ///广
// func bannerAdDidDismiss(bannerAd: VPBannerAd) {
// requestStatAd(type: "close", errorMsg: nil)
// }
// ///广
// func bannerAdDidClick(ad: VPBannerAd) {
// requestStatAd(type: "click", errorMsg: nil)
// }
//
//}
//
//extension VPBannerAdManager {
//
// func requestStatAd(type: String, errorMsg: String?) {
//
// var seconds = 0
// if let adsDate = self.adsDate {
// if type == "click" || type == "close" {
// seconds = Int(Date().timeIntervalSince(adsDate))
// }
// }
//
// if type == "close" && self.adsDate == nil { return }
//
// let model = VPStatAdModel()
// model.view_seconds = seconds
// model.type = type
// model.ads_id = bannerAd.adUnitID
// model.ad_platform_key = VPAdPlatformKey(rawValue: bannerAd.adPlatformKey)
// model.error_msg = errorMsg
// model.scene = .banner
// model.short_play_id = self.videoInfo?.short_play_id
// model.short_play_video_id = self.videoInfo?.short_play_video_id
//
// VPStatAPI.requestStatAd(model: model)
// }
//
//
// @objc private func didEnterBackgroundNotification() {
//
// self.requestStatAd(type: "Interrupt", errorMsg: nil)
// }
//}

View File

@ -0,0 +1,91 @@
////
//// VPApplovinRewardedAd.swift
//// Veloria
////
//// Created by on 2025/7/15.
////
//
//import UIKit
//#if canImport(AppLovinSDK)
//import AppLovinSDK
//#endif
//
//class VPApplovinRewardedAd: NSObject, VPRewardedAd {
//
//
//#if canImport(AppLovinSDK)
// private var rewardedAd: MARewardedAd?
//#endif
//
// weak var delegate: (any VPRewardedAdDelegate)?
//
// var adInfo: VPAdInfo? = nil
//
// var isReady: Bool {
//#if canImport(AppLovinSDK)
// return rewardedAd?.isReady ?? false
//#else
// return false
//#endif
// }
//
// func loadAd(adInfo: VPAdInfo) {
// self.adInfo = adInfo
//#if canImport(AppLovinSDK)
// rewardedAd = MARewardedAd.shared(withAdUnitIdentifier: adInfo.ads_id ?? "")
// rewardedAd?.delegate = self
// rewardedAd?.load()
//#endif
// }
//
// func showAd() {
//#if canImport(AppLovinSDK)
// if isReady {
// rewardedAd?.show()
// }
//#endif
// }
//
//
//}
//
//#if canImport(AppLovinSDK)
////MARK: -------------- MARewardedAdDelegate --------------
//extension VPApplovinRewardedAd: MARewardedAdDelegate {
//
// func didLoad(_ ad: MAAd) {
// self.delegate?.rewardedAdDidLoadFinish?(ad: self)
// }
//
// func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
//
// let nsError = NSError(domain: error.message, code: error.code.rawValue)
// self.delegate?.rewardedAd?(ad: self, didLoadFail: nsError)
// }
//
// func didDisplay(_ ad: MAAd) {
// self.delegate?.rewardedAdDidShow?(ad: self)
// }
//
// func didHide(_ ad: MAAd) {
// self.delegate?.rewardedAdDidDismiss?(ad: self)
// }
//
// func didClick(_ ad: MAAd) {
// self.delegate?.rewardedAdDidClick?(ad: self)
// }
//
// func didFail(toDisplay ad: MAAd, withError error: MAError) {
// let nsError = NSError(domain: error.message, code: error.code.rawValue)
// self.delegate?.rewardedAd?(ad: self, didDisplayFail: nsError)
// }
//
// func didRewardUser(for ad: MAAd, with reward: MAReward) {
// if let adInfo = self.adInfo {
// self.delegate?.rewardedAd?(ad: self, userDidEarnReward: adInfo)
// }
// }
//}
//#endif
//
//

View File

@ -0,0 +1,101 @@
////
//// VPGoogleRewardedAd.swift
//// Veloria
////
//// Created by on 2025/7/14.
////
//
//import UIKit
//#if canImport(GoogleMobileAds)
//import GoogleMobileAds
//#endif
//
//class VPGoogleRewardedAd: NSObject, VPRewardedAd {
//
//
//
//#if canImport(GoogleMobileAds)
// private var rewardedAd: RewardedAd?
//#endif
//
// weak var delegate: VPRewardedAdDelegate?
//
// var adInfo: VPAdInfo?
//
// var isReady: Bool {
//#if canImport(GoogleMobileAds)
// if rewardedAd != nil {
// return true
// } else {
// return false
// }
//#else
// return false
//#endif
// }
//
// func loadAd(adInfo: VPAdInfo) {
//#if canImport(GoogleMobileAds)
// self.adInfo = adInfo
//#if DEBUG
// adInfo.ads_id = VPAdManager.google_rewardedAdUnitID
//#endif
// let adUnitID = adInfo.ads_id ?? ""
// let request = Request()
//
// RewardedAd.load(with: adUnitID, request: request) { [weak self] rewardedAd, error in
// guard let self = self else { return }
// if let error = error {
// self.delegate?.rewardedAd?(ad: self, didLoadFail: error)
// return
// }
// self.rewardedAd = rewardedAd
// self.rewardedAd?.fullScreenContentDelegate = self
// self.delegate?.rewardedAdDidLoadFinish?(ad: self)
// }
//#endif
// }
//
// func showAd() {
//#if canImport(GoogleMobileAds)
// guard let rewardedAd = rewardedAd else { return }
//
// rewardedAd.present(from: nil) { [weak self] in
// guard let self = self else { return }
// guard let adInfo = self.adInfo else { return }
// self.delegate?.rewardedAd?(ad: self, userDidEarnReward: adInfo)
// }
//#endif
// }
//
//
//
//}
//
////MARK: -------------- FullScreenContentDelegate --------------
//#if canImport(GoogleMobileAds)
//extension VPGoogleRewardedAd: FullScreenContentDelegate {
//
// /// Tells the delegate that the ad failed to present full screen content.
// func ad(_ ad: FullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
// self.delegate?.rewardedAd?(ad: self, didDisplayFail: error)
// }
//
// /// Tells the delegate that the ad will present full screen content.
// func adWillPresentFullScreenContent(_ ad: FullScreenPresentingAd) {
// print("====Ad will present full screen content.")
// self.delegate?.rewardedAdDidShow?(ad: self)
// }
//
// /// Tells the delegate that the ad dismissed full screen content.
// func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) {
// print("====Ad did dismiss full screen content.")
// self.delegate?.rewardedAdDidDismiss?(ad: self)
// }
//
// func adDidRecordClick(_ ad: any FullScreenPresentingAd) {
// self.delegate?.rewardedAdDidClick?(ad: self)
// }
//
//}
//#endif

View File

@ -0,0 +1,292 @@
////
//// VPRewardedAdManager.swift
//// Veloria
////
//// Created by on 2025/7/14.
////
//
//import UIKit
//
//
//@objc protocol VPRewardedAdManagerDelegate: NSObjectProtocol {
//
// ///
// @objc optional func rewardedAdManager(adManager: VPRewardedAdManager, userDidEarnReward adInfo: VPAdInfo)
// ///广
// @objc optional func rewardedAdManagerDidLoadFinish(adManager: VPRewardedAdManager)
// ///广
// @objc optional func rewardedAdManager(adManager: VPRewardedAdManager, didLoadFail error: Error)
// ///广
// @objc optional func rewardedAdManager(adManager: VPRewardedAdManager, didDisplayFail error: Error)
// ///广
// @objc optional func rewardedAdManagerDidShow(adManager: VPRewardedAdManager)
// ///广
// @objc optional func rewardedAdManager(adManager: VPRewardedAdManager, didDismiss adInfo: VPAdInfo)
// ///广
// @objc optional func rewardedAdManagerDidClick(adManager: VPRewardedAdManager)
// ///
// @objc optional func rewardedAdManager(adManager: VPRewardedAdManager, didOtherFail error: Error)
//
//}
//
//@objc protocol VPRewardedAdDelegate: NSObjectProtocol {
//
// ///
// @objc optional func rewardedAd(ad: VPRewardedAd, userDidEarnReward adInfo: VPAdInfo)
// ///广
// @objc optional func rewardedAd(ad: VPRewardedAd, didLoadFail error: Error)
// ///广
// @objc optional func rewardedAdDidLoadFinish(ad: VPRewardedAd)
// ///广
// @objc optional func rewardedAd(ad: VPRewardedAd, didDisplayFail error: Error)
// ///广
// @objc optional func rewardedAdDidShow(ad: VPRewardedAd)
// ///广
// @objc optional func rewardedAdDidDismiss(ad: VPRewardedAd)
// ///广
// @objc optional func rewardedAdDidClick(ad: VPRewardedAd)
//}
//
//@objc protocol VPRewardedAd: NSObjectProtocol {
//
// weak var delegate: VPRewardedAdDelegate? { get set }
// var adInfo: VPAdInfo? { get set }
// var isReady: Bool { get }
//
// func loadAd(adInfo: VPAdInfo)
// func showAd()
//
//}
//
//class VPRewardedAdManager: NSObject {
//
// static let manager = VPRewardedAdManager()
//
// private weak var delegate: VPRewardedAdManagerDelegate?
//
// ///广
// private var isLoadingAd = false
// ///广
// private(set) var isEnable = true
//
// private var adInfo: VPAdInfo?
//
// private var adsDate: Date?
// ///
// var statScene: VPStatAdModel.AdScene?
// var videoInfo: VPVideoInfoModel?
//
// var rewardedAd: VPRewardedAd? {
// didSet {
// rewardedAd?.delegate = self
// }
// }
//
// var isReady: Bool {
// return rewardedAd?.isReady ?? false
// }
//
// deinit {
// NotificationCenter.default.removeObserver(self)
// }
//
// override init() {
// super.init()
// NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil)
// }
//
// func loadAd() {
// if isReady || isLoadingAd { return }
//
// self.isLoadingAd = true
//
// self.getAdInfo { [weak self] adInfo in
// guard let self = self else { return }
//
// switch adInfo.platform_key {
// case .google:
// let ad = VPGoogleRewardedAd()
// self.rewardedAd = ad
// ad.loadAd(adInfo: adInfo)
//
// case .applovin:
// let ad = VPApplovinRewardedAd()
// self.rewardedAd = ad
// ad.loadAd(adInfo: adInfo)
//
// default:
// self.adInfo = nil
// break
// }
// }
//
// }
//
// func showAd(delegate: VPRewardedAdManagerDelegate? = nil) {
// if isEnable && !isLoadingAd && !isReady {//广广广
// loadAd()
// }
//
// if !isEnable || !isReady {
// VPToast.show(text: "veloria_no_ads_tip".localized)
// let error = NSError(domain: "veloria_no_ads_tip".localized, code: -1)
// self.delegate?.rewardedAdManager?(adManager: self, didOtherFail: error)
// return
// }
//
// guard let rewardedAd = self.rewardedAd else { return }
// self.delegate = delegate
//
// rewardedAd.showAd()
// }
//
// private func clean() {
// self.rewardedAd = nil
// self.statScene = nil
// self.videoInfo = nil
// self.delegate = nil
// }
//}
//
////MARK: -------------- VPRewardedAdDelegate --------------
//extension VPRewardedAdManager: VPRewardedAdDelegate {
//
// ///
// func rewardedAd(ad: VPRewardedAd, userDidEarnReward adInfo: VPAdInfo) {
// self.requestStatAd(type: "reward", errorMsg: nil)
// self.delegate?.rewardedAdManager?(adManager: self, userDidEarnReward: adInfo)
// }
// ///广
// func rewardedAd(ad: VPRewardedAd, didLoadFail error: Error) {
// isEnable = false
// isLoadingAd = false
//
// self.requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
// self.delegate?.rewardedAdManager?(adManager: self, didLoadFail: error)
//
// self.clean()
// }
// ///广
// func rewardedAdDidLoadFinish(ad: VPRewardedAd) {
// isLoadingAd = false
//
// self.delegate?.rewardedAdManagerDidLoadFinish?(adManager: self)
// }
// ///广
// func rewardedAd(ad: VPRewardedAd, didDisplayFail error: Error) {
//
// self.requestStatAd(type: "show_failed", errorMsg: error.localizedDescription)
// self.delegate?.rewardedAdManager?(adManager: self, didDisplayFail: error)
//
// self.isEnable = false
// self.clean()
//
// /*
// if self.adInfo?.platform_key != .google {
// let adInfo = VPAdInfo()
// adInfo.platform_key = .google
// adInfo.ads_id = VPAdManager.google_rewardedAdUnitID
//
// let ad = VPGoogleRewardedAd()
// self.rewardedAd = ad
// ad.loadAd(adInfo: adInfo)
//
// } else {
// self.isEnable = false
// }
// */
//
// }
// ///广
// func rewardedAdDidShow(ad: VPRewardedAd) {
// self.adsDate = Date()
//
// self.requestStatAd(type: "start", errorMsg: nil)
// self.delegate?.rewardedAdManagerDidShow?(adManager: self)
// }
// ///广
// func rewardedAdDidDismiss(ad: VPRewardedAd) {
//
//
// var seconds = 0
// if let adsDate = self.adsDate {
// seconds = Int(Date().timeIntervalSince(adsDate))
// }
//
//
// self.requestStatAd(type: "close", seconds: seconds, errorMsg: nil) { [weak self] in
// guard let self = self else { return }
// if let adInfo = ad.adInfo {
// self.delegate?.rewardedAdManager?(adManager: self, didDismiss: adInfo)
// }
//
// self.clean()
// //广
// self.loadAd()
// }
//
// }
//
// ///广
// func rewardedAdDidClick(ad: VPRewardedAd) {
//
// var seconds = 0
// if let adsDate = self.adsDate {
// seconds = Int(Date().timeIntervalSince(adsDate))
// }
// self.requestStatAd(type: "click", seconds: seconds, errorMsg: nil)
//
// self.delegate?.rewardedAdManagerDidClick?(adManager: self)
// }
//
//}
//
//
//extension VPRewardedAdManager {
// private func requestStatAd(type: String, seconds: Int = 0, errorMsg: String?, completer: (() -> Void)? = nil) {
// guard let adInfo = adInfo else { return }
//
// let model = VPStatAdModel()
// model.type = type
// model.ads_id = adInfo.ads_id
// model.view_seconds = seconds
// model.ad_platform_key = adInfo.platform_key
// model.error_msg = errorMsg
// model.scene = self.statScene
// model.short_play_id = self.videoInfo?.short_play_id
// model.short_play_video_id = self.videoInfo?.short_play_video_id
//
// VPStatAPI.requestStatAd(model: model) { finish in
// completer?()
// }
// }
//
// @objc private func didEnterBackgroundNotification() {
// if self.statScene == nil { return }
//
// self.requestStatAd(type: "Interrupt", seconds: 0, errorMsg: nil)
// }
//
//
// private func getAdInfo(completer: ((_ adInfo: VPAdInfo) -> Void)?) {
//
// if let adInfo = self.adInfo {
// completer?(adInfo)
// }
// VPAdAPI.requestShowAdInfo { [weak self] adInfo in
// guard let self = self else { return }
//
// if let adInfo = adInfo {
// self.adInfo = adInfo
// completer?(adInfo)
// } else {
// let adInfo = VPAdInfo.createNormalAdInfo()
// self.adInfo = adInfo
// completer?(adInfo)
// }
//
// }
// }
//}
//
//

View File

@ -0,0 +1,40 @@
//
// VPAdInfo.swift
// Veloria
//
// Created by on 2025/7/14.
//
import UIKit
import SmartCodable
enum VPAdPlatformKey: String, SmartCaseDefaultable {
case google = "google"
case applovin = "applovin"
}
struct VPAdDataModel: SmartCodable {
var ad: VPAdInfo?
}
class VPAdInfo:NSObject, SmartCodable {
required override init() {
super.init()
}
var id: String?
var platform_name: String?
var ads_id: String?
var status: String?
var platform_key: VPAdPlatformKey?
static func createNormalAdInfo() -> VPAdInfo {
var adInfo = VPAdInfo()
adInfo.platform_key = .applovin
adInfo.ads_id = VPAdManager.applovin_rewardedAdUnitID
return adInfo
}
}

View File

@ -0,0 +1,100 @@
//
// VPAdManager.swift
// Veloria
//
// Created by on 2025/7/14.
//
import UIKit
#if canImport(GoogleMobileAds)
import GoogleMobileAds
#endif
#if canImport(AppLovinSDK)
import AppLovinSDK
#endif
class VPAdManager: NSObject {
static var completer: (() -> Void)?
static func start() {
#if canImport(GoogleMobileAds)
MobileAds.shared.start()
#endif
initialize_applovinSdk()
}
///AppLovinSDK
static func initialize_applovinSdk(completer: (() -> Void)? = nil) {
if completer != nil {
self.completer = completer
}
#if canImport(AppLovinSDK)
if !ALSdk.shared().isInitialized {
//
let initConfig = ALSdkInitializationConfiguration(sdkKey: "XW2aulJv9urKD4MIIFT1xcSCuyTHaDZ9qUbDqygnTLS04GkdX7WMQJviGP5vDRWGsk4OJJIyLGRV3mbLqOWx0W") { builder in
builder.mediationProvider = ALMediationProviderMAX
//#if DEBUG
// builder.testDeviceAdvertisingIdentifiers = [JXUUID.idfa()]
//#endif
}
ALSdk.shared().initialize(with: initConfig) { sdkConfig in
// Start loading ads
self.completer?()
}
} else {
self.completer?()
}
#endif
}
}
//MARK: -------------- ID --------------
extension VPAdManager {
///广ID
static var google_rewardedAdUnitID: String {
#if DEBUG
return "ca-app-pub-3940256099942544/1712485313"
#else
return ""
#endif
}
///广ID
static var google_appOpenAdUnitID: String {
#if DEBUG
return "ca-app-pub-3940256099942544/5575463023"
#else
return ""
#endif
}
///广ID
static var google_bannerAdUnitID: String {
#if DEBUG
return "ca-app-pub-3940256099942544/2435281174"
#else
return ""
#endif
}
///广ID
static var applovin_rewardedAdUnitID: String {
return "39bf981e27728eae"
}
///广ID
static var applovin_appOpenAdUnitID: String {
return "d587df0fd75ff03b"
}
///广ID
static var applovin_bannerAdUnitID: String {
return "1eb6278843149711"
}
}

View File

@ -0,0 +1,33 @@
//
// VPStatAdModel.swift
// Veloria
//
// Created by on 2025/7/14.
//
import UIKit
import SmartCodable
class VPStatAdModel: NSObject, SmartCodable {
required override init() {
super.init()
}
enum AdScene: String, SmartCaseDefaultable {
case detail = "detail"
case me = "me"
case reward = "reward"
case splash = "splash"
case banner = "banner"
}
var type: String? //start click error click show_failed load_failed Interrupt(退) close
var ads_id: String?
var view_seconds: Int?
var ad_platform_key: VPAdPlatformKey?
var scene: AdScene? // splash reward banner detail me turntable
var short_play_id: String?
var short_play_video_id: String?
var error_msg: String?
}

View File

@ -0,0 +1,137 @@
//
// SRAdmobOpenAd.swift
// SynthReel
//
// Created by on 2025/12/11.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
#if canImport(GoogleMobileAds)
import GoogleMobileAds
#endif
class SRAdmobOpenAd: NSObject, SRAppOpenAd {
#if canImport(GoogleMobileAds)
private var appOpenAd: AppOpenAd?
#endif
weak var delegate: SRAppOpenAdDelegate?
var adInfo: VPAdInfo?
var isShowingAd: Bool = false
// SRAppOpenAd adPlatformKey
var adPlatformKey: String {
return "AdMob" // 广
}
var isReady: Bool {
#if canImport(GoogleMobileAds)
return appOpenAd != nil
#else
return false
#endif
}
// SRAppOpenAd adUnitID
var adUnitID: String {
return adInfo?.ads_id ?? ""
}
// 广
func loadAd() {
#if canImport(GoogleMobileAds)
guard let adInfo = self.adInfo else { return }
let adUnitID = adInfo.ads_id ?? ""
let request = Request()
AppOpenAd.load(with: adUnitID, request: request) { [weak self] appOpenAd, error in
guard let self = self else { return }
if let error = error {
self.delegate?.appOpenAd?(ad: self, didLoadFail: error)
return
}
self.appOpenAd = appOpenAd
appOpenAd?.fullScreenContentDelegate = self
if let error = error {
self.delegate?.appOpenAd?(ad: self, didLoadFail: error)
} else {
//
self.delegate?.appOpenAdDidLoadFinish?(ad: self)
}
}
#endif
}
// 广
func showAd() {
#if canImport(GoogleMobileAds)
guard let appOpenAd = appOpenAd else {
print("App open ad is not ready yet.")
return
}
if isShowingAd {
return print("App open ad is already showing.")
}
appOpenAd.present(from: nil)
isShowingAd = true
#endif
}
}
// MARK: - FullScreenContentDelegate
#if canImport(GoogleMobileAds)
extension SRAdmobOpenAd: FullScreenContentDelegate {
func adDidRecordImpression(_ ad: FullScreenPresentingAd) {
print("App open ad recorded an impression.")
delegate?.appOpenAdDidShow?(ad: self)
}
func adDidRecordClick(_ ad: FullScreenPresentingAd) {
print("App open ad recorded a click.")
delegate?.appOpenAdDidClick?(ad: self)
}
func adWillDismissFullScreenContent(_ ad: FullScreenPresentingAd) {
print("App open ad will be dismissed.")
}
func adWillPresentFullScreenContent(_ ad: FullScreenPresentingAd) {
print("App open ad will be presented.")
delegate?.appOpenAdDidShow?(ad: self)
}
func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) {
print("App open ad was dismissed.")
//
delegate?.appOpenAdDidDismiss?(ad: self)
appOpenAd = nil
isShowingAd = false
// 广
loadAd()
}
func ad(_ ad: FullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
print("App open ad failed to present with error: \(error.localizedDescription)")
appOpenAd = nil
isShowingAd = false
delegate?.appOpenAd?(ad: self, didDisplayFail: error)
// 广
loadAd()
}
}
#endif

View File

@ -0,0 +1,227 @@
////
//// SROpenAdManager.swift
//// SynthReel
////
//// Created by on 2025/12/10.
//// Copyright © 2025 SR. All rights reserved.
////
//
import UIKit
import Foundation
import GoogleMobileAds
@objc protocol SRAppOpenAdManagerDelegate: NSObjectProtocol {
///广
@objc optional func appOpenAdManagerDidLoadFinish(adManager: SRAppOpenAdDelegate)
///广
@objc optional func appOpenAdManager(adManager: SRAppOpenAdDelegate, didLoadFail error: Error)
///广
@objc optional func appOpenAdManager(adManager: SRAppOpenAdDelegate, didDisplayFail error: Error)
///广
@objc optional func appOpenAdManagerDidShow(adManager: SRAppOpenAdDelegate)
///广
@objc optional func appOpenAdManagerDidDismiss(adManager: SRAppOpenAdDelegate)
///广
@objc optional func appOpenAdManagerDidClick(adManager: SRAppOpenAdDelegate)
///
@objc optional func appOpenAdManager(adManager: SRAppOpenAdDelegate, didOtherFail error: Error)
}
@objc protocol SRAppOpenAdDelegate: NSObjectProtocol {
///广
@objc optional func appOpenAd(ad: SRAppOpenAd, didLoadFail error: Error)
///广
@objc optional func appOpenAdDidLoadFinish(ad: SRAppOpenAd)
///广
@objc optional func appOpenAd(ad: SRAppOpenAd, didDisplayFail error: Error)
///广
@objc optional func appOpenAdDidShow(ad: SRAppOpenAd)
///广
@objc optional func appOpenAdDidDismiss(ad: SRAppOpenAd)
///广
@objc optional func appOpenAdDidClick(ad: SRAppOpenAd)
}
@objc protocol SRAppOpenAd: NSObjectProtocol {
weak var delegate: SRAppOpenAdDelegate? { get set }
var adPlatformKey: String { get }
var adUnitID: String { get }
var isReady: Bool { get }
func loadAd()
func showAd()
}
class SRAppOpenAdManager: NSObject {
static let manager = SRAppOpenAdManager()
weak var delegate: SRAppOpenAdManagerDelegate?
private(set) var appOpenAd: SRAppOpenAd? {
didSet {
oldValue?.delegate = nil
appOpenAd?.delegate = self
}
}
private var isLoading = false
private var isShowing = false
///
private var needAutoShow = false
private var timeOutTimer: Timer?
private var adsDate: Date?
deinit {
NotificationCenter.default.removeObserver(self)
}
override init() {
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
func showAdIfAvailable() {
if self.appOpenAd?.isReady == true {
self.showAd()
} else {
self.timeOutTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(handleTimeOutTimer), userInfo: nil, repeats: false)
self.needAutoShow = true
self.loadAd()
}
}
func loadAd() {
guard !isLoading else { return }
self.isLoading = true
appOpenAd = SRAdmobOpenAd()
appOpenAd?.loadAd()
}
func showAd() {
guard self.appOpenAd?.isReady == true else { return }
guard !self.isShowing else { return }
self.isShowing = true
self.appOpenAd?.showAd()
}
@objc private func handleTimeOutTimer() {
self.needAutoShow = false
cleanTimer()
let error = NSError(domain: "time-out", code: -1)
requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
self.delegate?.appOpenAdManager?(adManager: self, didOtherFail: error)
}
private func cleanTimer() {
self.timeOutTimer?.invalidate()
self.timeOutTimer = nil
}
private func clean() {
appOpenAd = nil
delegate = nil
isShowing = false
self.adsDate = nil
}
}
//MARK: -------------- VPAppOpenAdDelegate --------------
extension SRAppOpenAdManager: SRAppOpenAdDelegate {
///广
func appOpenAd(ad: SRAppOpenAd, didLoadFail error: Error) {
cleanTimer()
isLoading = false
requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
self.delegate?.appOpenAdManager?(adManager: self, didLoadFail: error)
clean()
}
///广
func appOpenAdDidLoadFinish(ad: SRAppOpenAd) {
cleanTimer()
self.isLoading = false
self.delegate?.appOpenAdManagerDidLoadFinish?(adManager: self)
if needAutoShow {
self.needAutoShow = false
self.showAd()
}
}
///广
func appOpenAd(ad: SRAppOpenAd, didDisplayFail error: Error) {
requestStatAd(type: "show_failed", errorMsg: error.localizedDescription)
self.delegate?.appOpenAdManager?(adManager: self, didDisplayFail: error)
clean()
}
///广
func appOpenAdDidShow(ad: SRAppOpenAd) {
adsDate = Date()
requestStatAd(type: "start", errorMsg: nil)
self.delegate?.appOpenAdManagerDidShow?(adManager: self)
}
///广
func appOpenAdDidDismiss(ad: SRAppOpenAd) {
requestStatAd(type: "close", errorMsg: nil)
self.delegate?.appOpenAdManagerDidDismiss?(adManager: self)
clean()
}
///广
func appOpenAdDidClick(ad: SRAppOpenAd) {
requestStatAd(type: "click", errorMsg: nil)
self.delegate?.appOpenAdManagerDidClick?(adManager: self)
}
}
extension SRAppOpenAdManager {
private func requestStatAd(type: String, errorMsg: String?) {
guard let appOpenAd = self.appOpenAd else { return }
var seconds = 0
if let adsDate = self.adsDate {
if type == "click" || type == "close" {
seconds = Int(Date().timeIntervalSince(adsDate))
}
}
let model = VPStatAdModel()
model.type = type
model.view_seconds = seconds
model.ads_id = appOpenAd.adUnitID
model.ad_platform_key = VPAdPlatformKey(rawValue: appOpenAd.adPlatformKey)
model.error_msg = errorMsg
model.scene = .splash
// VPStatAPI.requestStatAd(model: model)
}
@objc private func didEnterBackgroundNotification() {
guard self.isShowing else { return }
self.requestStatAd(type: "Interrupt", errorMsg: nil)
}
}

View File

@ -0,0 +1,101 @@
//
// SRAdManger.swift
// SynthReel
//
// Created by on 2025/12/11.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
#if canImport(GoogleMobileAds)
import GoogleMobileAds
#endif
#if canImport(AppLovinSDK)
import AppLovinSDK
#endif
class SRAdManger: NSObject {
static var completer: (() -> Void)?
static func start() {
#if canImport(GoogleMobileAds)
MobileAds.shared.start()
#endif
initialize_applovinSdk()
}
///AppLovinSDK
static func initialize_applovinSdk(completer: (() -> Void)? = nil) {
if completer != nil {
self.completer = completer
}
#if canImport(AppLovinSDK)
if !ALSdk.shared().isInitialized {
//
let initConfig = ALSdkInitializationConfiguration(sdkKey: "XW2aulJv9urKD4MIIFT1xcSCuyTHaDZ9qUbDqygnTLS04GkdX7WMQJviGP5vDRWGsk4OJJIyLGRV3mbLqOWx0W") { builder in
builder.mediationProvider = ALMediationProviderMAX
//#if DEBUG
// builder.testDeviceAdvertisingIdentifiers = [JXUUID.idfa()]
//#endif
}
ALSdk.shared().initialize(with: initConfig) { sdkConfig in
// Start loading ads
self.completer?()
}
} else {
self.completer?()
}
#endif
}
}
//MARK: -------------- ID --------------
extension SRAdManger {
///广ID
static var google_rewardedAdUnitID: String {
#if DEBUG
return "ca-app-pub-3940256099942544/1712485313"
#else
return "ca-app-pub-6275401933109862/2121481439"
#endif
}
///广ID
static var google_appOpenAdUnitID: String {
#if DEBUG
return "ca-app-pub-3940256099942544/5575463023"
#else
return "ca-app-pub-6275401933109862/7130265535"
#endif
}
///广ID
static var google_bannerAdUnitID: String {
#if DEBUG
return "ca-app-pub-3940256099942544/2435281174"
#else
return "ca-app-pub-6275401933109862/7971191024"
#endif
}
///广ID
static var applovin_rewardedAdUnitID: String {
return "3708ac6b6026e375"
}
// ///广ID
// static var applovin_appOpenAdUnitID: String {
// return "d587df0fd75ff03b"
// }
// ///广ID
// static var applovin_bannerAdUnitID: String {
// return "1eb6278843149711"
// }
}

View File

@ -2,7 +2,7 @@
// SRDefine.swift
// SynthReel
//
// Created by on 2025/11/12.
// Created by on 2025/11/12.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit

View File

@ -2,7 +2,7 @@
// SRUserDefaultsKey.swift
// SynthReel
//
// Created by on 2025/11/12.
// Created by on 2025/11/12.
// Copyright © 2025 SR. All rights reserved.
//
@ -12,3 +12,12 @@ let kSRAccountTokenDefaultsKey = "kSRAccountTokenDefaultsKey"
///
let kSRUserInfoDefaultsKey = "kSRUserInfoDefaultsKey"
let kSRWaitRestoreIAPDefaultsKey = "kSRWaitRestoreIAPDefaultsKey"
let kSRApnsAlertDefaultsKey = "kSRApnsAlertDefaultsKey"
let kSRVersionUpdateAlertDefaultsKey = "kSRVersionUpdateAlertDefaultsKey"
let kSRHasBeenOpenedAPPDefaultsKey = "kSRHasBeenOpenedAPPDefaultsKey"

View File

@ -0,0 +1,24 @@
//
// Dictionary+SRAdd.swift
// SynthReel
//
// Created by on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
extension Dictionary {
func toJsonString() -> String? {
do {
let data = try JSONSerialization.data(withJSONObject: self)
let jsonStr = String(data: data, encoding: .utf8)
return jsonStr
} catch {
}
return nil
}
}

View File

@ -2,7 +2,7 @@
// NSNumber+SRAdd.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,17 +2,19 @@
// String+SRAdd.swift
// SynthReel
//
// Created by on 2025/11/13.
// Created by on 2025/11/13.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import YYCategories
import SmartCodable
extension String {
var localized: String {
return String(localized: LocalizationValue(self))
let value = String(localized: LocalizationValue(self))
return value.replacingOccurrences(of: "<br>", with: "\n")
}
func localizedReplace(text: String) -> String {
@ -24,3 +26,32 @@ extension String {
}
}
extension String: SmartCodable {
static func timeZone() -> String {
let timeZone = NSTimeZone.local as NSTimeZone
let timeZoneSecondsFromGMT = timeZone.secondsFromGMT / 3600
return String(format: "GMT+0%d:00", timeZoneSecondsFromGMT)
}
}
extension String {
///url
func urlQuryToDictionary() -> [String : Any] {
let array = self.components(separatedBy: "&")
var tempDic: [String : Any] = [:]
array.forEach {
if let strRange = $0.range(of: "=") {
var key: String = String($0.prefix(upTo: strRange.upperBound))
key.removeLast()
var value: String = String($0.suffix(from: strRange.upperBound))
value = value.removingPercentEncoding ?? value
tempDic[key] = value
}
}
return tempDic
}
}

View File

@ -2,7 +2,7 @@
// UIFont+SRAdd.swift
// SynthReel
//
// Created by on 2025/11/13.
// Created by on 2025/11/13.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// UINavigationBar+SRAdd.swift
// SynthReel
//
// Created by CSGY on 2025/11/25.
// Created by on 2025/11/25.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// UIScreen+SRAdd.swift
// SynthReel
//
// Created by on 2025/11/13.
// Created by on 2025/11/13.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// UIScrollView+SRAdd.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// UIStackView+SRAdd.swift
// SynthReel
//
// Created by on 2025/11/15.
// Created by on 2025/11/15.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// UIView+SRAdd.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// UserDefaults+SRAdd.swift
// SynthReel
//
// Created by on 2025/11/12.
// Created by on 2025/11/12.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -0,0 +1,26 @@
//
// SROpenAppModel.swift
// SynthReel
//
// Created by on 2025/12/9.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
struct SROpenAppModel: SmartCodable {
enum Path: String, SmartCaseDefaultable {
case videoDetail = "detail"
///
case feedback = "feedback"
///
case promotion = "promotion"
}
var id: String?
var message_id: String?
var short_play_id: String?
var path: Path?
}

View File

@ -0,0 +1,39 @@
//
// SRVersionUpdateModel.swift
// SynthReel
//
// Created by on 2025/12/10.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
struct SRVersionUpdateModel: SmartCodable {
var version_code: String?
var des: String?
var version_name: String?
var force: Bool?
func canUpdate() -> Bool {
guard let version = version_code else { return false }
let result = kSRAPPVersion.compare(version, options: .numeric)
if result == .orderedAscending {
return true
} else {
return false
}
}
static func mappingForKey() -> [SmartKeyTransformer]? {
return [
CodingKeys.des <--- ["description"]
]
}
}

View File

@ -2,7 +2,7 @@
// SRNetwork.swift
// SynthReel
//
// Created by on 2025/11/12.
// Created by on 2025/11/12.
// Copyright © 2025 SR. All rights reserved.
//
@ -20,7 +20,8 @@ class SRNetwork: NSObject {
static func request<T>(parameters: SRNetwork.Parameters, completion: ((_ response: SRNetwork.Response<T>) -> Void)?) {
if SRAccountManager.manager.token == nil {
if SRLogin.manager.token == nil {
print(SRLogin.manager.token ?? "123")
self.requestToken(completer: nil)
}
@ -48,11 +49,15 @@ class SRNetwork: NSObject {
static func _request<T>(parameters: SRNetwork.Parameters, completion: ((_ response: SRNetwork.Response<T>) -> Void)?) -> Cancellable {
if parameters.isLoding {
SRHud.show()
DispatchQueue.main.async {
SRHud.show()
}
}
return provider.request(.request(parameters: parameters)) { result in
if parameters.isLoding {
SRHud.dismiss()
DispatchQueue.main.async {
SRHud.dismiss()
}
}
guard let completion = completion else {return}
_resultDispose(parameters: parameters, result: result, completion: completion)
@ -81,7 +86,7 @@ class SRNetwork: NSObject {
self.requestToken { token in
if token != nil {
_Concurrency.Task {
await SRAccountManager.manager.updateUserInfo()
await SRLogin.manager.requestUserToken(completer: nil)
}
}
}
@ -164,7 +169,7 @@ class SRNetwork: NSObject {
extension SRNetwork {
///token
static func requestToken(completer: ((_ token: SRAccountToken?) -> Void)?) {
static func requestToken(completer: ((_ token: SRTokenModel?) -> Void)?) {
guard self.tokenOperation == nil else {
completer?(nil)
return
@ -173,11 +178,11 @@ extension SRNetwork {
self.tokenOperation = BlockOperation(block: {
let semaphore = DispatchSemaphore(value: 0)
let param = SRNetwork.Parameters(path: "/customer/register")
print("chongfu")
DispatchQueue.main.async {
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRAccountToken>) in
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRTokenModel>) in
if let token = response.data {
SRAccountManager.manager.setAccountToken(token)
SRLogin.manager.loginSuccessSaveToken(token)
}
do { semaphore.signal() }
self.tokenOperation = nil
@ -189,5 +194,4 @@ extension SRNetwork {
operationQueue.addOperation(self.tokenOperation!)
}
}

View File

@ -2,7 +2,7 @@
// SRNetworkModel.swift
// SynthReel
//
// Created by on 2025/11/12.
// Created by on 2025/11/12.
// Copyright © 2025 SR. All rights reserved.
//
@ -30,7 +30,7 @@ extension SRNetwork {
var data: T?
var msg: String?
@SmartIgnored
@IgnoredKey
var rawData: Any?
var isSuccess: Bool {

View File

@ -2,7 +2,7 @@
// SRNetworkReachableManager.swift
// SynthReel
//
// Created by on 2025/11/12.
// Created by on 2025/11/12.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRResponseCryptor.swift
// SynthReel
//
// Created by on 2025/11/12.
// Created by on 2025/11/12.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRTargetType.swift
// SynthReel
//
// Created by on 2025/11/12.
// Created by on 2025/11/12.
// Copyright © 2025 SR. All rights reserved.
//
@ -56,7 +56,7 @@ extension SRTargetType: TargetType {
"device-id" : SRDeviceId.shared.id, //id
"system-type" : "ios",
"model" : UIDevice.current.machineModelName ?? "",
"authorization" : SRAccountManager.manager.token?.token ?? "",
"authorization" : SRLogin.manager.token?.token ?? "",
"device-gaid" : UIDevice.current.identifierForVendor?.uuidString ?? "",
"product-prefix" : "SynthReel"
]

View File

@ -2,16 +2,27 @@
// SRUrlPath.swift
// SynthReel
//
// Created by on 2025/11/12.
// 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"
let SRBaseURL = "https://api-synthreeltv.synthreeltv.com"
let SRUrlPathPrefix = "/th"
let SRWebBaseURL = "https://www.synthreeltv.com"
let SRCampaignWebURL = "https://campaign.synthreeltv.com"
//@"https://api-univicos.univicos.com/uni";
///
let kSRFeedBackHomeWebUrl = SRCampaignWebURL + "/pages/leave/index"
///
let kSRFeedBackListWebUrl = SRCampaignWebURL + "/pages/leave/list"
///
let kSRFeedBackDetailWebUrl = SRCampaignWebURL + "/pages/leave/detail"
///
let kSRLogoutWebUrl = SRCampaignWebURL + "/pages/setting/logout"
let kSRRewardWebUrl = SRCampaignWebURL + "/pages/reward/theme4"
/*
SynthReel
admin-api https://admin-api-synthreeltv.synthreeltv.com

View File

@ -2,11 +2,12 @@
// SRCollectionView.swift
// SynthReel
//
// Created by on 2025/11/14.
// Created by on 2025/11/14.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import LYEmptyView
class SRCollectionView: UICollectionView {

View File

@ -2,7 +2,7 @@
// SRGradientView.swift
// SynthReel
//
// Created by on 2025/11/15.
// Created by on 2025/11/15.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -0,0 +1,45 @@
//
// SRGradientbutton.swift
// SynthReel
//
// Created by on 2025/11/27.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRGradientbutton: UIButton {
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
var fa_gradientLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
var fa_colors: [CGColor]? {
didSet {
fa_gradientLayer.colors = fa_colors
}
}
var fa_startPoint: CGPoint = .zero {
didSet {
fa_gradientLayer.startPoint = fa_startPoint
}
}
var fa_endPoint: CGPoint = .zero {
didSet {
fa_gradientLayer.endPoint = fa_endPoint
}
}
var fa_locations: [NSNumber] = [] {
didSet {
fa_gradientLayer.locations = fa_locations
}
}
}

View File

@ -2,7 +2,7 @@
// SRImageView.swift
// SynthReel
//
// Created by on 2025/11/15.
// Created by on 2025/11/15.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRLabel.swift
// SynthReel
//
// Created by on 2025/11/14.
// Created by on 2025/11/14.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRNavgationTitleView.swift
// SynthReel
//
// Created by CSGY on 2025/11/25.
// Created by on 2025/11/25.
// Copyright © 2025 SR. All rights reserved.
//
@ -26,7 +26,7 @@ class SRNavgationTitleView: UIView {
private let titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 18, weight: .bold)
label.font = .font(ofSize: 18, weight: .bold).withBoldItalic()
label.textAlignment = .center
label.textColor = .white
return label

View File

@ -2,7 +2,7 @@
// SRPanModalContentView.swift
// SynthReel
//
// Created by on 2025/11/18.
// Created by on 2025/11/18.
// Copyright © 2025 SR. All rights reserved.
//
@ -12,16 +12,20 @@ import SnapKit
class SRPanModalContentView: HWPanModalContentView {
var mainScrollView: UIScrollView?
var contentHeight = UIScreen.height * (2 / 3)
var bgImage: UIImage? = UIImage(named: "pan_bg_image_01")
///UI contentSize
func setNeedsLayoutUpdate() {
self.panModalSetNeedsLayoutUpdate()
}
lazy var bgImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "pan_bg_image_01"))
let imageView = UIImageView(image: bgImage)
return imageView
}()
@ -35,15 +39,28 @@ class SRPanModalContentView: HWPanModalContentView {
}
}
//
func updateBackgroundImage(_ img: UIImage?) {
self.bgImage = img
self.bgImageView.image = img
}
//
func updateContentHeight(_ height: CGFloat) {
self.contentHeight = height
self.panModalSetNeedsLayoutUpdate()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//MARK: HWPanModalPresentable
override func panScrollable() -> UIScrollView? {
return nil
return mainScrollView
}
override func longFormHeight() -> PanModalHeight {
return PanModalHeightMake(.content, contentHeight)
}

View File

@ -2,7 +2,7 @@
// SRScrollView.swift
// SynthReel
//
// Created by on 2025/11/15.
// Created by on 2025/11/15.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRTableView.swift
// SynthReel
//
// Created by on 2025/11/18.
// Created by on 2025/11/18.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRTableViewCell.swift
// SynthReel
//
// Created by on 2025/11/18.
// Created by on 2025/11/18.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -0,0 +1,53 @@
//
// SRAppOpenAdViewController.swift
// SynthReel
//
// Created by on 2025/12/11.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRAppOpenAdViewController: SRViewController {
var didEndBlock: (() -> Void)?
private(set) lazy var lanuchVC: UIViewController? = {
let vc = SRTool.lanuchViewController
return vc
}()
override func viewDidLoad() {
super.viewDidLoad()
if let vc = lanuchVC {
addChild(vc)
view.addSubview(vc.view)
}
let manager = SRAppOpenAdManager.manager
manager.delegate = self
manager.showAdIfAvailable()
}
}
//MARK: -------------- VPAppOpenAdManagerDelegate --------------
extension SRAppOpenAdViewController: SRAppOpenAdManagerDelegate {
func appOpenAdManagerDidDismiss(adManager: SRAppOpenAdDelegate) {
self.didEndBlock?()
}
func appOpenAdManager(adManager: SRAppOpenAdDelegate, didLoadFail error: any Error) {
self.didEndBlock?()
}
func appOpenAdManager(adManager: SRAppOpenAdDelegate, didDisplayFail error: any Error) {
self.didEndBlock?()
}
func appOpenAdManager(adManager: SRAppOpenAdDelegate, didOtherFail error: any Error) {
self.didEndBlock?()
}
}

View File

@ -0,0 +1,52 @@
//
// SRAppStartViewController.swift
// SynthReel
//
// Created by on 2025/12/9.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRAppStartViewController: SRViewController {
var openAppBlock: (() -> Void)?
private lazy var lanuchVC: UIViewController? = {
let vc = SRTool.lanuchViewController
return vc
}()
private lazy var startButton: UIButton = {
let button = SRGradientbutton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
self.openAppBlock?()
}))
button.setBackgroundImage(.startButton, for: .normal)
button.setTitle("start", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = .font(ofSize:16, weight: .semibold)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
if let vc = lanuchVC {
addChild(vc)
view.addSubview(vc.view)
}
view.addSubview(startButton)
startButton.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(48)
make.height.equalTo(50)
make.bottom.equalToSuperview().offset(-(UIScreen.safeBottom + 110))
}
}
}

View File

@ -2,7 +2,7 @@
// SRNavigationController.swift
// SynthReel
//
// Created by on 2025/11/13.
// Created by on 2025/11/13.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRTabBarController.swift
// SynthReel
//
// Created by on 2025/11/13.
// Created by on 2025/11/13.
// Copyright © 2025 SR. All rights reserved.
//
@ -32,6 +32,9 @@ class SRTabBarController: ESTabBarController {
self.tabBar.standardAppearance = appearance
self.tabBar.scrollEdgeAppearance = appearance
self.tabBar.isTranslucent = false
// SRTool.checkUpdates()
}

View File

@ -2,7 +2,7 @@
// SRViewController.swift
// SynthReel
//
// Created by on 2025/11/13.
// Created by on 2025/11/13.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -0,0 +1,83 @@
//
// SRAppWebViewController.swift
// SynthReel
//
// Created by on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRAppWebViewController: SRBaseWebViewController {
var id: String?
private var receiveDataCount = 0
var theme: String? = "theme_16"
override func viewDidLoad() {
super.viewDidLoad()
self.autoTitle = false
if webUrl == kSRFeedBackListWebUrl {
self.title = "synthreel_feedback_history".localized
} else if webUrl == kSRFeedBackHomeWebUrl {
self.title = "synthreel_feedback".localized
} else if webUrl == kSRFeedBackDetailWebUrl {
self.title = "synthreel_feedback_detail".localized
} else if webUrl == kSRLogoutWebUrl {
self.title = "synthreel_account_deletion".localized
}else if webUrl == kSRRewardWebUrl {
self.title = "Rewards".localized
}
}
override func fa_webViewDidFinishLoad(_ webView: SRWebView) {
super.fa_webViewDidFinishLoad(webView)
receiveDataCount = 0
receiveDataFromNative()
}
}
extension SRAppWebViewController {
func receiveDataFromNative() {
receiveDataCount += 1
if receiveDataCount > 10 { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let self = self else { return }
var dic = [
"token" : SRLogin.manager.token?.token ?? "",
"time_zone" : String.timeZone(),
// "lang" : FALanguageManager.manager.currentLanguageKey,
"lang" : "en",
"type" : "ios",
"device-id" : SRDeviceId.shared.id
]
if let theme = theme {
dic["theme"] = theme
}
if let id = id {
dic["id"] = id
}
if let json = dic.toJsonString() {
let js = "receiveDataFromNative(\(json))"
self.webView.evaluateJavaScript(js) { [weak self] _, error in
guard let self = self else { return }
if error != nil {
self.receiveDataFromNative()
}
}
}
}
}
}

View File

@ -0,0 +1,112 @@
//
// SRBaseWebViewController + Script.swift
// SynthReel
//
// Created by on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import WebKit
import ZLPhotoBrowser
import SmartCodable
///APP
let kSRWebMessageAPP = "js2app"
///
let kSRWebMessageOpenFeedbackList = "openFeedbackList"
///
let kSRWebMessageOpenFeedbackDetail = "openFeedbackDetail"
///
let kSRWebMessageOpenPhotoPicker = "openPhotoPicker"
///
let kSRWebMessageAccountDeletionFinish = "accountLogout"
extension SRBaseWebViewController {
func fa_webViewUserContentController(didReceive message: WKScriptMessage) {
let name = message.name
let body = message.body
switch name {
case kSRWebMessageOpenFeedbackList:
let vc = SRAppWebViewController()
vc.webUrl = kSRFeedBackListWebUrl
self.navigationController?.pushViewController(vc, animated: true)
case kSRWebMessageOpenFeedbackDetail:
guard let body = body as? [String : Any] else { return }
guard let id = body["id"] as? Int else { return }
let vc = SRAppWebViewController()
vc.id = "\(id)"
vc.webUrl = kSRFeedBackDetailWebUrl
self.navigationController?.pushViewController(vc, animated: true)
case kSRWebMessageOpenPhotoPicker:
openPhotoPicker()
case kSRWebMessageAPP:
guard let body = message.body as? [String : Any] else { return }
guard let model = SRWebMessageModel.deserialize(from: body) else { return }
let type = model.type
let data = model.data
if type == "login" {
// let view = FALoginView()
// view.present(in: nil)
} else if type == "open_notify" {
// openNotify()
} else if type == "watch_video" {
let vc = SRDetailPlayerViewController()
vc.shortId = data?.short_play_id
// vc.activityId = data?.activity_id
self.navigationController?.pushViewController(vc, animated: true)
} else {
guard let urlStr = data?.link else { return }
guard let url = URL(string: urlStr) else { return }
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
}
case kSRWebMessageAccountDeletionFinish:
self.navigationController?.popToRootViewController(animated: true)
NotificationCenter.default.post(name: SRLogin.loginStatusChangeNotification, object: nil)
default:
break
}
}
///
private func openPhotoPicker() {
ZLPhotoConfiguration.default().allowSelectOriginal = false
ZLPhotoConfiguration.default().maxSelectCount = 1
ZLPhotoConfiguration.default().allowEditImage = false
ZLPhotoConfiguration.default().allowSelectVideo = false
ZLPhotoConfiguration.default().allowSelectGif = false
ZLPhotoConfiguration.default().allowTakePhotoInLibrary = false
let picker = ZLPhotoPicker()
picker.selectImageBlock = { [weak self] (results, _) in
guard let self = self else { return }
guard let image = results.first?.image else { return }
guard let imageData = image.jpegData(compressionQuality: 0.8) else { return }
let imageDataStr = imageData.base64EncodedString(options: .endLineWithCarriageReturn)
let js = "uploadConvertImage('\(imageDataStr)')"
self.webView.evaluateJavaScript(js)
}
picker.showPhotoLibrary(sender: self)
}
}

View File

@ -0,0 +1,114 @@
//
// SRBaseWebViewController.swift
// SynthReel
//
// Created by on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import WebKit
class SRBaseWebViewController: SRViewController {
var webUrl: String?
///
var autoTitle = true
var needAutoRefresh = true
private(set) lazy var webView: SRWebView = {
let controller = WKUserContentController()
let config = WKWebViewConfiguration()
config.userContentController = controller
config.preferences.javaScriptEnabled = true
/** JS */
config.preferences.javaScriptCanOpenWindowsAutomatically = true
let webView = SRWebView(frame: self.view.bounds, configuration: config)
webView.delegate = self
return webView
}()
override func viewDidLoad() {
super.viewDidLoad()
// self.edgesForExtendedLayout = []
fa_setupLayout()
if let url = webUrl {
self.load(webUrl: url)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
// self.fa_setNavigationStyle()
}
func load(webUrl: String) {
let str: String = webUrl
guard let url = URL(string: str) else { return }
let request = URLRequest(url: url, timeoutInterval: 30)
self.webView.load(request)
}
func reload() {
self.webView.reload()
}
}
extension SRBaseWebViewController {
private func fa_setupLayout() {
self.view.addSubview(webView)
self.webView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(0)
make.right.equalToSuperview().offset(0)
make.bottom.equalToSuperview().offset(0)
make.top.equalToSuperview().offset(UIScreen.navBarHeight)
}
}
}
//MARK: -------------- VPWebViewDelegate --------------
extension SRBaseWebViewController: SRWebViewDelegate {
func fa_webView(_ webView: SRWebView, shouldStartLoadWith navigationAction: WKNavigationAction) -> Bool {
self.webView.isHidden = false
return true
}
func fa_webViewDidStartLoad(_ webView: SRWebView) {
SRHud.show(containerView: self.view)
}
func fa_webView(webView: SRWebView, didChangeTitle title: String) {
if autoTitle {
self.title = title
}
}
func fa_webViewDidFinishLoad(_ webView: SRWebView) {
self.webView.isHidden = false
SRHud.dismiss()
}
func fa_webView(_ webView: SRWebView, didFailLoadWithError error: any Error) {
SRHud.dismiss()
}
func fa_userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
self.fa_webViewUserContentController(didReceive: message)
}
}

View File

@ -0,0 +1,26 @@
//
// SRWebMessageModel.swift
// SynthReel
//
// Created by on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
struct SRWebMessageModel: SmartCodable {
var type: String?
var data: SRWebMessageData?
}
struct SRWebMessageData: SmartCodable {
var activity_id: String?
var short_play_id: String?
var link: String?
}

View File

@ -0,0 +1,153 @@
//
// SRWebView.swift
// SynthReel
//
// Created by on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
@preconcurrency import WebKit
import YYText
@objc protocol SRWebViewDelegate: NSObjectProtocol {
@objc optional func fa_webView(_ webView: SRWebView, shouldStartLoadWith navigationAction: WKNavigationAction) -> Bool
@objc optional func fa_webViewDidStartLoad(_ webView: SRWebView)
@objc optional func fa_webViewDidFinishLoad(_ webView: SRWebView)
@objc optional func fa_webView(_ webView: SRWebView, didFailLoadWithError error: Error)
///
@objc optional func fa_webView(webView: SRWebView, didChangeProgress progress: CGFloat)
///
@objc optional func fa_webView(webView: SRWebView, didChangeTitle title: String)
///web
@objc optional func fa_userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
}
class SRWebView: WKWebView {
weak var delegate: SRWebViewDelegate?
private(set) var scriptMessageHandlerArray: [String] = [
kSRWebMessageAPP,
kSRWebMessageOpenFeedbackList,
kSRWebMessageOpenFeedbackDetail,
kSRWebMessageOpenPhotoPicker,
kSRWebMessageAccountDeletionFinish,
]
deinit {
self.removeObserver(self, forKeyPath: "estimatedProgress")
self.removeObserver(self, forKeyPath: "title")
}
override init(frame: CGRect, configuration: WKWebViewConfiguration) {
super.init(frame: frame, configuration: configuration)
addScriptMessageHandler()
_setupInit()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func _setupInit() {
self.isOpaque = false
self.navigationDelegate = self
self.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
self.addObserver(self, forKeyPath: "title", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object as? SRWebView == self {
if keyPath == "estimatedProgress", let progress = change?[NSKeyValueChangeKey.newKey] as? CGFloat {
self.delegate?.fa_webView?(webView: self, didChangeProgress: progress)
} else if keyPath == "title", let title = change?[NSKeyValueChangeKey.newKey] as? String {
self.delegate?.fa_webView?(webView: self, didChangeTitle: title)
}
}
}
func load(urlStr: String) {
guard let url = URL(string: urlStr) else { return }
let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 30)
self.load(request)
}
func removeScriptMessageHandler() {
self.scriptMessageHandlerArray.forEach{
configuration.userContentController.removeScriptMessageHandler(forName: $0)
}
}
func addScriptMessageHandler() {
self.scriptMessageHandlerArray.forEach{
configuration.userContentController.add(YYTextWeakProxy(target: self) as! WKScriptMessageHandler, name: $0)
}
}
}
//MARK:-------------- WKNavigationDelegate --------------
extension SRWebView: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
decisionHandler(.allow);
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url,
url.scheme != "http",
url.scheme != "https"
{
UIApplication.shared.open(url)
decisionHandler(.cancel)
return
}
if let result = self.delegate?.fa_webView?(self, shouldStartLoadWith: navigationAction) {
if result {
decisionHandler(.allow)
} else {
decisionHandler(.cancel)
}
} else {
decisionHandler(.allow)
}
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
self.delegate?.fa_webViewDidStartLoad?(self)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.delegate?.fa_webViewDidFinishLoad?(self)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
self.delegate?.fa_webView?(self, didFailLoadWithError: error)
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
self.delegate?.fa_webView?(self, didFailLoadWithError: error)
}
}
//MARK:-------------- WKScriptMessageHandler --------------
extension SRWebView: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
self.delegate?.fa_userContentController?(userContentController, didReceive: message)
}
}

View File

@ -0,0 +1,14 @@
//
// SRCoinPackCanReceiveModel.swift
// SynthReel
//
// Created by on 2025/12/9.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
struct SRCoinPackCanReceiveModel: SmartCodable {
var coins: Int?
}

View File

@ -0,0 +1,26 @@
//
// SRCoinsPackModel.swift
// SynthReel
//
// Created by on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
struct SRCoinsPackModel: SmartCodable {
//
var receive_count: Int?
//
var week_total: Int?
//
var week_remaining_total: Int?
//
var week_max_total: Int?
//
var receive_coins: Int?
var receive_list: [SRCoinsPackReceiveModel]?
}

View File

@ -0,0 +1,26 @@
//
// SRCoinsPackReceiveModel.swift
// SynthReel
//
// Created by on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
struct SRCoinsPackReceiveModel: SmartCodable {
var id: String?
var title: String?
//
var week_remaining_total: Int?
//
var receive_coins: Int?
//
var week_total: Int?
//
var week_max_total: Int?
//
var day_text: String?
}

View File

@ -0,0 +1,246 @@
//
// SRCoinPackController.swift
// SynthReel
//
// Created by on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRCoinPackController: SRViewController {
private var model: SRCoinsPackModel? {
didSet {
headerView.model = model
claimView.dataArr = model?.receive_list ?? []
}
}
private var payDataModel : SRPayDateModel? {
didSet {
var arr: [SRPayItem] = []
payDataModel?.list_coins?.forEach {
if $0.buy_type == .subCoins {
arr.append($0)
}
}
self.buyView.dataArr = arr
}
}
private var payDataRequest: SRPayDataRequest?
lazy var topImageview = UIImageView.init(image: .topCoinPack)
private lazy var bgIconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "查看图片 25"))
return imageView
}()
private lazy var scrollView: SRScrollView = {
let scrollView = SRScrollView()
return scrollView
}()
private lazy var headerView: SRCoinPackHeaderView = {
let view = SRCoinPackHeaderView()
view.clickClaimButton = { [weak self] in
self?.requestReceiveCoins(nil)
}
return view
}()
private lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 19
return stackView
}()
private lazy var claimView: SRCoinsPackClaimListView = {
let view = SRCoinsPackClaimListView()
view.clickClaimButton = { [weak self] id in
guard let self = self else { return }
self.requestReceiveCoins(id)
}
return view
}()
private lazy var buyView: SRCoinsPackBuyView = {
let view = SRCoinsPackBuyView()
view.buyFinishHandle = { [weak self] in
self?.requestCoinsPackData()
}
return view
}()
private lazy var tipView: UIView = {
let view = UIView()
view.addSubview(tipTitleLabel)
view.addSubview(tipTextLabel)
tipTitleLabel.snp.makeConstraints { make in
make.top.equalToSuperview()
make.left.equalToSuperview().offset(16)
}
tipTextLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.right.lessThanOrEqualToSuperview().offset(-16)
make.top.equalTo(tipTitleLabel.snp.bottom).offset(4)
make.bottom.equalToSuperview()
}
return view
}()
private lazy var tipTitleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .medium)
label.textColor = .white.withAlphaComponent(0.5)
label.text = "coins_pack_tips_title".localized
return label
}()
private weak var recommandView: UIView?
private lazy var tipTextLabel: UILabel = {
let att = NSMutableAttributedString(string: "coins_pack_tips".localized)
att.yy_lineSpacing = 3
let label = UILabel()
label.font = .font(ofSize: 10, weight: .regular)
label.textColor = .white.withAlphaComponent(0.5)
label.attributedText = att
label.numberOfLines = 0
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
()
self.navigationItem.title = "My Refills".localized
payDataModel = SRIapManager.manager.payDateModel
sr_setui()
requestCoinsPackData()
updateLayout()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
private func updateLayout() {
stackView.sr_removeAllArrangedSubview()
if self.claimView.dataArr.count > 0 {
stackView.addArrangedSubview(self.claimView)
} else if self.buyView.dataArr.count > 0 {
stackView.addArrangedSubview(self.buyView)
}
stackView.addArrangedSubview(tipView)
}
}
extension SRCoinPackController {
func sr_setui() {
view.addSubview(topImageview)
topImageview.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
}
view.addSubview(bgIconImageView)
view.addSubview(scrollView)
scrollView.addSubview(headerView)
scrollView.addSubview(stackView)
bgIconImageView.snp.makeConstraints { make in
make.right.equalToSuperview()
make.top.equalToSuperview()
}
scrollView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalToSuperview().offset(UIScreen.navBarHeight)
make.bottom.equalToSuperview()
}
headerView.snp.makeConstraints { make in
make.left.centerX.equalToSuperview()
make.top.equalToSuperview()
}
stackView.snp.makeConstraints { make in
make.left.centerX.equalToSuperview()
make.top.equalTo(headerView.snp.bottom).offset(16)
make.bottom.equalToSuperview().offset(-(UIScreen.safeBottom + 10))
}
}
}
extension SRCoinPackController {
private func requestCoinsPackData() {
Task {
let model = await SRStoreAPI.requestCoinsPackData()
guard let model = model else { return }
self.model = model
if (model.receive_list?.count ?? 0) == 0 {
self.requestPayData()
}
self.updateLayout()
}
}
private func requestPayData() {
self.payDataRequest = SRPayDataRequest()
self.payDataRequest?.requestProducts { [weak self] model in
guard let self = self else { return }
guard let model = model else { return }
self.payDataModel = model
self.updateLayout()
}
}
private func requestReceiveCoins(_ id: String?) {
Task {
let sucess = await SRStoreAPI.requestReceiveCoinsPackCoins(id: id)
self.requestCoinsPackData()
if sucess {
self.showVideoRecommand()
}
}
}
///
private func showVideoRecommand() {
guard self.recommandView == nil else { return }
Task {
let (model, _, _) = await SRShortApi.requestRecommand()
guard let model = model else { return }
guard self.recommandView == nil else { return }
let view = SRDetailRecommendview()
view.dataArr = model
view.didSelectedVideo = { [weak self] model in
guard let self = self else { return }
let vc = SRDetailPlayerViewController()
vc.shortId = model.short_play_id
self.navigationController?.pushViewController(vc, animated: true)
}
view.show(in: SRTool.keyWindow)
self.recommandView = view
}
}
}

View File

@ -0,0 +1,82 @@
//
// SRCoinPackConfirmItem1View.swift
// SynthReel
//
// Created by on 2025/12/4.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRCoinPackConfirmItem1View: SRCoinPackConfirmItemView {
override init(frame: CGRect) {
super.init(frame: frame)
lazy var bgIconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "getBg"))
return imageView
}()
titleLabel.text = "synthreel_bonus_you_get".localized
let view1 = getItemView(image: UIImage(named: "dailyIocn"), text: "synthreel_weekly_refill_package".localized)
let view2 = getItemView(image: UIImage(named: "rewardicon"), text: "synthreel_dailu_bonuses".localized)
addSubview(bgIconImageView)
addSubview(view1)
addSubview(view2)
bgIconImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
view1.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.top.equalTo(titleLabel.snp.bottom).offset(8)
make.height.equalTo(83)
make.bottom.equalToSuperview().offset(-16)
}
view2.snp.makeConstraints { make in
make.left.equalTo(view1.snp.right).offset(12)
make.right.equalToSuperview().offset(-12)
make.width.height.top.equalTo(view1)
}
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func getItemView(image: UIImage?, text: String) -> UIView {
let view = UIImageView()
view.image = .itemBg
let imageView = UIImageView(image: image)
imageView.setContentHuggingPriority(.required, for: .horizontal)
imageView.setContentCompressionResistancePriority(.required, for: .horizontal)
let label = UILabel()
label.numberOfLines = 0
label.font = .font(ofSize: 12, weight: .init(800))
label.textColor = .white
label.text = text
label.textAlignment = .center
view.addSubview(imageView)
view.addSubview(label)
imageView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(5)
}
label.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(10)
make.top.equalTo(imageView.snp.bottom).offset(10)
}
return view
}
}

View File

@ -0,0 +1,134 @@
//
// SRCoinPackConfirmItem2View.swift
// SynthReel
//
// Created by on 2025/12/4.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRCoinPackConfirmItem2View: SRCoinPackConfirmItemView {
var list: [String]? {
didSet {
self.stackView.sr_removeAllArrangedSubview()
list?.forEach {
let view = SRCoinPackConfirmItem2View.ItemView()
view.text = $0
self.stackView.addArrangedSubview(view)
}
}
}
private lazy var bgIconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "receiveBg"))
return imageView
}()
private lazy var lineView: UIView = {
let view = UIView()
view.backgroundColor = .white.withAlphaComponent(0.1)
return view
}()
private lazy var stackView: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 24
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.titleLabel.text = "synthreel_coin_bag_buy_tip_title".localized
fa_setupLayout()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SRCoinPackConfirmItem2View {
private func fa_setupLayout() {
addSubview(bgIconImageView)
addSubview(lineView)
addSubview(stackView)
bgIconImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
lineView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.centerX.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(16)
make.height.equalTo(1)
}
stackView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.centerX.equalToSuperview()
make.top.equalTo(lineView.snp.bottom).offset(16)
make.bottom.equalToSuperview().offset(-16)
}
}
}
extension SRCoinPackConfirmItem2View {
class ItemView: UIView {
var text: String? {
didSet {
textLabel.text = text
}
}
private lazy var iconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "coins_icon_06"))
return imageView
}()
private lazy var textLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 14, weight: .regular)
label.textColor = .white
label.numberOfLines = 0
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(iconImageView)
addSubview(textLabel)
iconImageView.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.equalToSuperview()
}
textLabel.snp.makeConstraints { make in
make.left.equalTo(iconImageView.snp.right).offset(12)
make.right.lessThanOrEqualToSuperview()
make.top.equalToSuperview()
make.bottom.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}

View File

@ -0,0 +1,41 @@
//
// SRCoinPackConfirmItemView.swift
// SynthReel
//
// Created by on 2025/12/4.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRCoinPackConfirmItemView: UIView {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 18, weight: .bold)
label.textColor = .white
label.textAlignment = .center
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .black
layer.cornerRadius = 8
layer.masksToBounds = true
addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.right.lessThanOrEqualToSuperview().offset(-16)
make.top.equalToSuperview().offset(16)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,207 @@
//
// SRCoinPackConfirmView.swift
// SynthReel
//
// Created by on 2025/12/4.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import HWPanModal
class SRCoinPackConfirmView: SRPanModalContentView {
var buyFinishHandle: (() -> Void)?
var shortPlayId: String?
var videoId: String?
var model: SRPayItem? {
didSet {
item2View.list = model?.ext_info?.sub_coins_txt_list
var price = model?.price ?? ""
if model?.discount_type == 1, let introductoryPrice = model?.introductionaryOffer {
price = introductoryPrice.price.stringValue
} else if model?.discount_type == 2, let discount = model?.promotionalOffers?.first {
price = discount.price.stringValue
}
priceLabel.text = "\(model?.currency ?? "")\(price)/\(model?.getTimeString() ?? "")"
}
}
// private lazy var bgImageView: UIImageView = {
// let imageView = UIImageView(image: UIImage(named: ""))
// return imageView
// }()
private lazy var bgIconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: ""))
return imageView
}()
private lazy var titleLabel: SRLabel = {
let label = SRLabel()
label.font = .font(ofSize: 24, weight: .init(700))
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)
label.text = "synthreel_what_you_get".localized
return label
}()
private lazy var closeButton: UIButton = {
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
Task {
await self.dismiss(animated: true)
}
}))
button.setImage(UIImage(named: "Close"), for: .normal)
return button
}()
private lazy var scrollView: SRScrollView = {
let scrollView = SRScrollView()
scrollView.addObserver(self, forKeyPath: "contentSize", context: nil)
return scrollView
}()
private lazy var item1View: SRCoinPackConfirmItem1View = {
let view = SRCoinPackConfirmItem1View()
return view
}()
private lazy var item2View: SRCoinPackConfirmItem2View = {
let view = SRCoinPackConfirmItem2View()
return view
}()
private lazy var priceLabel: SRLabel = {
let label = SRLabel()
label.font = .font(ofSize: 18, weight: .bold)
label.textColors = [UIColor.srBlue.cgColor, UIColor.srBlue.cgColor]
label.textStartPoint = .init(x: 0, y: 0.5)
label.textEndPoint = .init(x: 1, y: 0.5)
return label
}()
private lazy var continueButton: UIButton = {
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
guard let model = self.model else { return }
SRIapManager.manager.start(model: model, shortPlayId: self.shortPlayId, videoId: self.videoId) { [weak self] finish in
guard let self = self else { return }
if finish {
self.buyFinishHandle?()
Task {
await self.dismiss(animated: true)
}
}
}
}))
button.setTitle("synthreel_continue".localized, for: .normal)
button.setTitleColor(.black, for: .normal)
button.titleLabel?.font = .font(ofSize: 14, weight: .bold)
button.setBackgroundImage(.continueBG, for: .normal)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .clear
sr_setupLayout()
}
@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 == "contentSize" {
self.panModalSetNeedsLayoutUpdate()
}
}
override func allowsTapBackgroundToDismiss() -> Bool {
return false
}
override func cornerRadius() -> CGFloat {
return 0
}
override func longFormHeight() -> PanModalHeight {
let height = 96 + scrollView.contentSize.height + 92 + 10
return PanModalHeightMake(.content, height + UIScreen.safeBottom)
}
}
extension SRCoinPackConfirmView {
private func sr_setupLayout() {
addSubview(bgImageView)
bgImageView.addSubview(bgIconImageView)
addSubview(titleLabel)
addSubview(closeButton)
addSubview(scrollView)
scrollView.addSubview(item1View)
scrollView.addSubview(item2View)
addSubview(priceLabel)
addSubview(continueButton)
bgImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
bgIconImageView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(55)
}
closeButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-16)
make.top.equalToSuperview().offset(24)
}
scrollView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalToSuperview().offset(96)
make.bottom.equalToSuperview().offset(-(92 + UIScreen.safeBottom + 10))
}
item1View.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.centerX.equalToSuperview()
make.top.equalToSuperview()
}
item2View.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.centerX.equalToSuperview()
make.top.equalTo(item1View.snp.bottom).offset(12)
make.bottom.equalToSuperview()
}
priceLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.centerY.equalTo(scrollView.snp.bottom).offset(22)
}
continueButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.left.equalToSuperview().offset(16)
make.top.equalTo(scrollView.snp.bottom).offset(44)
make.height.equalTo(48)
}
}
}

View File

@ -0,0 +1,328 @@
//
// SRCoinPackHeaderView.swift
// SynthReel
//
// Created by on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRCoinPackHeaderView: UIView {
var clickClaimButton: (() -> Void)?
var model: SRCoinsPackModel? {
didSet {
coinsView1.coins = model?.week_max_total
coinsView2.coins = model?.week_total
activeCountLabel.text = "\(model?.receive_count ?? 0)"
if let coin = model?.receive_coins, coin > 0 {
claimButton.isEnabled = true
} else {
claimButton.isEnabled = false
}
claimButton.setNeedsUpdateConfiguration()
}
}
private lazy var titleView: UILabel = {
let view = UILabel()
view.font = .font(ofSize: 14, weight: .regular)
view.textColor = .white
view.text = "synthreel_rewards_overview".localized.uppercased()
return view
}()
private lazy var coinsView1: CoinsView = {
let view = CoinsView()
view.title = "synthreel_weekly_total".localized
view.coins = 0
return view
}()
private lazy var coinsView2: CoinsView = {
let view = CoinsView()
view.title = "synthreel_claimable_coins".localized
view.coins = 0
return view
}()
private lazy var lineView1: UIView = {
let view = UIImageView(image: UIImage(named: "间隔线"))
return view
}()
private lazy var lineView2: UIView = {
let view = UIImageView(image: UIImage(named: "横间隔虚线"))
return view
}()
private lazy var activeCountTitleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .regular)
label.textColor = .white.withAlphaComponent(0.5)
label.text = "synthreel_active_refills".localized + ": "
return label
}()
private lazy var activeCountLabel: SRLabel = {
let label = SRLabel()
label.font = .font(ofSize: 12, weight: .medium)
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)
label.text = "0"
return label
}()
private lazy var claimButton: UIButton = {
let button = UIButton(type: .custom)
// 使
let updateHandler: UIButton.ConfigurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
var config = UIButton.Configuration.plain()
if button.isEnabled {
//
config.background.image = .claimAllHiglit
config.background.imageContentMode = .scaleToFill
//
let coinImage = UIImage(named: "smallCoins")!
let coinText = NSTextAttachment(image: coinImage)
coinText.bounds = CGRect(x: 0, y: -2.5, width: 13, height: 13)
let coinAtt = AttributedString(NSAttributedString(attachment: coinText))
let textAtt = AttributedString("synthreel_claim_all".localized + " ", attributes: AttributeContainer([
.font: UIFont.font(ofSize: 14, weight: .bold),
.foregroundColor: UIColor.white
]))
let countAtt = AttributedString(" \(self.model?.receive_coins ?? 0)", attributes: AttributeContainer([
.font: UIFont.font(ofSize: 14, weight: .bold),
.foregroundColor: UIColor.white
]))
config.attributedTitle = textAtt + coinAtt + countAtt
} else {
//
config.background.image = .claimAllNomarl
config.background.imageContentMode = .scaleToFill
config.attributedTitle = AttributedString("synthreel_get_refill_button_text".localized, attributes: AttributeContainer([
.font: UIFont.font(ofSize: 14, weight: .bold),
.foregroundColor: UIColor.white.withAlphaComponent(0.5)
]))
}
//
config.contentInsets = NSDirectionalEdgeInsets(top: 12, leading: 20, bottom: 12, trailing: 20)
//
button.configuration = config
}
//
button.configurationUpdateHandler = updateHandler
//
button.addAction(UIAction(handler: { [weak self] _ in
self?.clickClaimButton?()
}), for: .touchUpInside)
//
button.isEnabled = false
button.configurationUpdateHandler?(button) //
return button
}()
// private lazy var claimButton: UIButton = {
// let config = UIButton.Configuration.plain()
// let button = UIButton(configuration: config, primaryAction: UIAction(handler: { [weak self] _ in
// self?.clickClaimButton?()
// }))
// button.isEnabled = false
// button.configurationUpdateHandler = { [weak self] button in
// guard let self = self else { return }
// guard let button = button as? UIButton else { return }
// if button.isEnabled {
//// button.fa_colors = [UIColor._53_A_2_F_1.cgColor, UIColor.C_5_DDF_5.cgColor]
// button.setBackgroundImage(.claimAllHiglit, for: .normal)
// let coinImage = UIImage(named: "smallCoins")!
// let coinText = NSTextAttachment(image: coinImage)
// coinText.bounds = .init(x: 0, y: -2.5, width: coinImage.size.width, height: coinImage.size.height)
// let coinAtt = AttributedString(NSAttributedString(attachment: coinText))
//
// let textAtt = AttributedString("synthreel_claim_all".localized + " ", attributes: AttributeContainer([
// .font : UIFont.font(ofSize: 14, weight: .bold),
// .foregroundColor : UIColor.white
// ]))
//
// let countAtt = AttributedString(" \(self.model?.receive_coins ?? 0)".localized, attributes: AttributeContainer([
// .font : UIFont.font(ofSize: 14, weight: .bold),
// .foregroundColor : UIColor.white
// ]))
//
// button.configuration?.attributedTitle = textAtt + coinAtt + countAtt
//
//
// } else {
//// button.fa_colors = [UIColor._6_D_6_D_6_D.cgColor, UIColor._6_D_6_D_6_D.cgColor]
// button.setBackgroundImage(.claimAllNomarl, for: .normal)
// button.configuration?.attributedTitle = AttributedString("synthreel_get_refill_button_text".localized, attributes: AttributeContainer([
// .font : UIFont.font(ofSize: 14, weight: .bold),
// .foregroundColor : UIColor.white.withAlphaComponent(0.5)
// ]))
// }
//
// }
// return button
// }()
override init(frame: CGRect) {
super.init(frame: frame)
fa_setupLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SRCoinPackHeaderView {
private func fa_setupLayout() {
addSubview(titleView)
addSubview(coinsView1)
addSubview(coinsView2)
addSubview(lineView1)
addSubview(activeCountTitleLabel)
addSubview(activeCountLabel)
addSubview(claimButton)
addSubview(lineView2)
titleView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.top.equalToSuperview().offset(20)
}
coinsView1.snp.makeConstraints { make in
make.left.equalToSuperview().offset(28)
make.top.equalTo(titleView.snp.bottom).offset(23)
}
lineView1.snp.makeConstraints { make in
make.centerY.equalTo(coinsView1)
make.left.equalTo(coinsView1.snp.right).offset(18)
}
coinsView2.snp.makeConstraints { make in
make.centerY.equalTo(lineView1)
make.left.equalTo(lineView1.snp.right).offset(18)
}
activeCountTitleLabel.snp.makeConstraints { make in
make.left.equalTo(coinsView1)
make.top.equalTo(coinsView1.snp.bottom).offset(23)
}
activeCountLabel.snp.makeConstraints { make in
make.left.equalTo(activeCountTitleLabel.snp.right)
make.centerY.equalTo(activeCountTitleLabel)
}
claimButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(23)
make.centerX.equalToSuperview()
make.top.equalTo(activeCountTitleLabel.snp.bottom).offset(12)
make.height.equalTo(48)
make.bottom.equalToSuperview().offset(-16)
}
lineView2.snp.makeConstraints { make in
make.left.equalTo(claimButton)
make.right.equalTo(claimButton)
make.bottom.equalToSuperview()
}
}
}
extension SRCoinPackHeaderView {
class CoinsView: UIView {
var title: String? {
didSet {
titleLabel.text = title
}
}
var coins: Int? {
didSet {
coinsLabel.text = "\(coins ?? 0)"
}
}
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .bold)
label.textColor = .white.withAlphaComponent(0.5)
return label
}()
private lazy var iconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "smallCoins"))
return imageView
}()
private lazy var coinsLabel: SRLabel = {
let label = SRLabel()
label.font = .font(ofSize: 18, weight: .bold)
label.textColors = [UIColor.srBlue.cgColor, UIColor.srBlue.cgColor]
label.textStartPoint = .init(x: 0, y: 0.5)
label.textEndPoint = .init(x: 1, y: 0.5)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(titleLabel)
addSubview(iconImageView)
addSubview(coinsLabel)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.equalToSuperview()
make.right.lessThanOrEqualToSuperview()
}
iconImageView.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(4)
make.bottom.equalToSuperview()
}
coinsLabel.snp.makeConstraints { make in
make.centerY.equalTo(iconImageView)
make.left.equalTo(iconImageView.snp.right).offset(4)
make.right.lessThanOrEqualToSuperview()
}
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}

View File

@ -0,0 +1,124 @@
//
// SRCoinsPackAlert.swift
// SynthReel
//
// Created by on 2025/12/9.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRCoinsPackAlert: SRBaseAlert {
var claimButtonClick: (() -> Void)?
var coinsCount: Int? {
didSet {
}
}
private lazy var imageView = UIImageView(image: UIImage(named: "coinalertBg"))
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 18, weight: .bold)
label.textColor = .white
label.textAlignment = .center
label.numberOfLines = 0
label.text = "coins_pack_alert_title".localized
return label
}()
private lazy var moreCoinimageView = UIImageView(image: UIImage(named: "moreCoins"))
private lazy var coinLabel: SRLabel = {
let label = SRLabel()
label.textColors = [UIColor.F_1_FF_2_F.cgColor, UIColor.srBlue.cgColor]
label.textStartPoint = .init(x: 0, y: 0.5)
label.textEndPoint = .init(x: 1, y: 0.5)
label.font = .font(ofSize: 20, weight: .semibold)
return label
}()
private lazy var claimButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("synthreel_claim_now".localized, for: .normal)
button.setTitleColor(.black, for: .normal)
button.titleLabel?.font = .font(ofSize: 16, weight: .semibold)
button.setBackgroundImage(.cliamNowBg, for: .normal)
button.addTarget(self, action: #selector(claimButtonAciton), for: .touchUpInside)
return button
}()
private lazy var laterButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("synthreel_open_notification_later".localized, for: .normal)
button.setTitleColor(.FFFFFF_40, for: .normal)
button.titleLabel?.font = .font(ofSize: 14, weight: .regular)
button.addTarget(self, action: #selector(dismiss), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
fa_setupLayout()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func claimButtonAciton () {
self.claimButtonClick?()
}
}
extension SRCoinsPackAlert {
private func fa_setupLayout() {
contentView.addSubview(imageView)
imageView.addSubview(titleLabel)
imageView.addSubview(moreCoinimageView)
imageView.addSubview(coinLabel)
imageView.addSubview(claimButton)
imageView.addSubview(laterButton)
imageView.isUserInteractionEnabled = true
imageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.left.equalTo(17)
make.right.lessThanOrEqualToSuperview().offset(-110)
make.top.equalTo(15)
}
moreCoinimageView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(35)
}
coinLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(moreCoinimageView.snp.bottom).offset(2)
}
claimButton.snp.makeConstraints { make in
make.top.equalTo(coinLabel.snp.bottom).offset(35)
make.left.right.equalToSuperview().inset(17)
make.height.equalTo(48)
}
laterButton.snp.makeConstraints { make in
make.left.right.height.equalTo(claimButton)
make.top.equalTo(claimButton.snp.bottom).offset(1)
make.bottom.equalToSuperview().offset(-13)
}
}
}

View File

@ -0,0 +1,114 @@
//
// SRCoinsPackBuyView.swift
// SynthReel
//
// Created by on 2025/12/4.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRCoinsPackBuyView: UIView {
var buyFinishHandle: (() -> Void)?
var dataArr: [SRPayItem] = []
private lazy var titleView: UILabel = {
let view = UILabel()
view.textColor = .white
view.font = .font(ofSize: 14, weight: .regular)
view.text = "synthreel_weekly_refill".localized.uppercased()
return view
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: UIScreen.width - 32, height: 84)
layout.minimumLineSpacing = 12
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(SRPackCoinViewCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
deinit {
collectionView.removeObserver(self, forKeyPath: "contentSize")
}
override init(frame: CGRect) {
super.init(frame: frame)
fa_setupLayout()
}
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 + 1
collectionView.snp.updateConstraints { make in
make.height.equalTo(height)
}
}
}
}
extension SRCoinsPackBuyView {
private func fa_setupLayout() {
addSubview(titleView)
addSubview(collectionView)
titleView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.centerX.equalToSuperview()
}
collectionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalToSuperview().offset(30)
make.height.equalTo(1)
make.bottom.equalToSuperview()
}
}
}
//MARK: UICollectionViewDelegate UICollectionViewDataSource
extension SRCoinsPackBuyView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SRPackCoinViewCell
cell.model = self.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 = self.dataArr[indexPath.row]
let view = SRCoinPackConfirmView()
view.model = model
view.buyFinishHandle = { [weak self] in
guard let self = self else { return }
Task {
await SRLogin.manager.requestUserInfo(completer: nil)
self.buyFinishHandle?()
}
}
view.present(in: nil)
}
}

View File

@ -0,0 +1,264 @@
//
// SRCoinsPackClaimListCell.swift
// SynthReel
//
// Created by on 2025/12/4.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRCoinsPackClaimListCell: UICollectionViewCell {
var clickClaimButton: ((_ id: String?) -> Void)?
var model: SRCoinsPackReceiveModel? {
didSet {
coinsView1.coins = model?.week_max_total
coinsView2.coins = model?.week_remaining_total
claimButton.isEnabled = (model?.receive_coins ?? 0) > 0
claimButton.setNeedsUpdateConfiguration()
let titleAtt = NSMutableAttributedString(string: "\(model?.title ?? "")")
titleAtt.yy_color = .white
titleAtt.yy_font = .font(ofSize: 14, weight: .bold)
let day = "synthreel_d".localized
let dayAtt = NSMutableAttributedString(string: " (\(day) \(model?.day_text ?? ""))")
dayAtt.yy_color = .white
dayAtt.yy_font = .font(ofSize: 14, weight: .regular)
titleAtt.append(dayAtt)
titleLabel.attributedText = titleAtt
}
}
private lazy var bgView: UIImageView = {
let view = UIImageView()
view.image = .claimCellbg
view.contentMode = .scaleToFill
view.isUserInteractionEnabled = true
return view
}()
private lazy var bgIconImageView1 = UIImageView(image: UIImage(named: "coin_attachment_01"))
private lazy var bgIconImageView2 = UIImageView(image: UIImage(named: "coin_attachment_03"))
private lazy var bgIconImageView4 = UIImageView(image: UIImage(named: "coin_attachment_04"))
private lazy var bgIconImageView5 = UIImageView(image: UIImage(named: "coin_attachment_05"))
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
return label
}()
private lazy var lineView1 = UIImageView(image: UIImage(named: "横间隔虚线"))
private lazy var lineView2 = UIImageView(image: UIImage(named: "间隔线"))
private lazy var coinsView1: CoinsView = {
let view = CoinsView()
view.title = "synthreel_total_reward".localized
return view
}()
private lazy var coinsView2: CoinsView = {
let view = CoinsView()
view.title = "synthreel_remaining".localized
return view
}()
private lazy var claimButton: UIButton = {
var config = UIButton.Configuration.plain()
config.titleAlignment = .center
config.background.image = .coinStackBg
let button = UIButton(configuration: config, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
self.clickClaimButton?(self.model?.id)
}))
button.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
if button.isEnabled {
button.configuration?.background.image = UIImage(named: "coinStackBg")
let coinImage = UIImage(named: "bigCoins")!
let coinText = NSTextAttachment(image: coinImage)
coinText.bounds = .init(x: 0, y: -2.5, width: 13, height: 13)
let coinAtt = AttributedString(NSAttributedString(attachment: coinText))
let countAtt = AttributedString(" \(self.model?.receive_coins ?? 0)", attributes: AttributeContainer([
.font : UIFont.font(ofSize: 12, weight: .bold),
.foregroundColor : UIColor._000000.withAlphaComponent(0.5)
]))
button.configuration?.attributedTitle = AttributedString("synthreel_claim".localized, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 14, weight: .bold),
.foregroundColor : UIColor.white
]))
button.configuration?.attributedSubtitle = coinAtt + countAtt
} else {
button.configuration?.background.image = UIImage(named: "claimd_button_bg")
button.configuration?.attributedTitle = AttributedString("synthreel_claimd".localized, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 14, weight: .bold),
.foregroundColor : UIColor.FFFFFF_40
]))
button.configuration?.attributedSubtitle = nil
}
}
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
fa_setupLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SRCoinsPackClaimListCell {
private func fa_setupLayout() {
contentView.addSubview(bgView)
bgView.addSubview(bgIconImageView5)
bgView.addSubview(bgIconImageView4)
bgView.addSubview(bgIconImageView1)
bgView.addSubview(bgIconImageView2)
bgView.addSubview(titleLabel)
bgView.addSubview(lineView1)
bgView.addSubview(lineView2)
bgView.addSubview(coinsView1)
bgView.addSubview(coinsView2)
bgView.addSubview(claimButton)
bgView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
bgIconImageView1.snp.makeConstraints { make in
make.left.top.equalToSuperview()
}
bgIconImageView2.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.right.equalToSuperview().offset(-8)
}
bgIconImageView4.snp.makeConstraints { make in
make.top.equalToSuperview()
make.centerX.equalTo(bgIconImageView2)
}
bgIconImageView5.snp.makeConstraints { make in
make.bottom.equalToSuperview()
make.left.equalToSuperview().offset(0)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.centerY.equalTo(self.bgView.snp.top).offset(25)
make.right.lessThanOrEqualToSuperview().offset(-12)
}
lineView1.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(48)
}
coinsView1.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.bottom.equalToSuperview().offset(-14)
}
lineView2.snp.makeConstraints { make in
make.centerY.equalTo(coinsView1)
make.left.equalTo(coinsView1.snp.right).offset(12)
}
coinsView2.snp.makeConstraints { make in
make.centerY.equalTo(coinsView1)
make.left.equalTo(lineView2.snp.right).offset(12)
}
claimButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-12)
make.bottom.equalTo(-14)
make.height.equalTo(48)
make.width.equalTo(120)
}
}
}
extension SRCoinsPackClaimListCell {
class CoinsView: UIView {
var title: String? {
didSet {
titleLabel.text = title
}
}
var coins: Int? {
didSet {
coinsLabel.text = "\(coins ?? 0)"
}
}
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .bold)
label.textColor = .white
return label
}()
private lazy var iconImageView = UIImageView(image: UIImage(named: "bigCoins"))
private lazy var coinsLabel: UILabel = {
let label = SRLabel()
label.font = .font(ofSize: 14, weight: .bold)
label.textColor = .white
label.text = "0"
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(titleLabel)
addSubview(iconImageView)
addSubview(coinsLabel)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.equalToSuperview()
make.right.lessThanOrEqualToSuperview()
}
iconImageView.snp.makeConstraints { make in
make.left.equalToSuperview()
make.bottom.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(8)
}
coinsLabel.snp.makeConstraints { make in
make.centerY.equalTo(iconImageView)
make.left.equalTo(iconImageView.snp.right).offset(4)
make.right.lessThanOrEqualToSuperview()
}
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}

View File

@ -0,0 +1,106 @@
//
// SRCoinsPackClaimListView.swift
// SynthReel
//
// Created by on 2025/12/4.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRCoinsPackClaimListView: UIView {
var clickClaimButton: ((_ id: String?) -> Void)?
var dataArr: [SRCoinsPackReceiveModel] = [] {
didSet {
collectionView.reloadData()
}
}
private lazy var titleView: UILabel = {
let view = UILabel()
view.textColor = .white
view.font = .font(ofSize: 14, weight: .regular)
view.text = "synthreel_refills".localized.uppercased()
return view
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: UIScreen.width - 32, height: 122)
layout.minimumLineSpacing = 12
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(SRCoinsPackClaimListCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
deinit {
collectionView.removeObserver(self, forKeyPath: "contentSize")
}
override init(frame: CGRect) {
super.init(frame: frame)
fa_setupLayout()
}
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 + 1
collectionView.snp.updateConstraints { make in
make.height.equalTo(height)
}
}
}
}
extension SRCoinsPackClaimListView {
private func fa_setupLayout() {
addSubview(titleView)
addSubview(collectionView)
titleView.snp.makeConstraints { make in
make.top.equalToSuperview()
make.centerX.equalToSuperview()
}
collectionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalToSuperview().offset(30)
make.height.equalTo(1)
make.bottom.equalToSuperview()
}
}
}
//MARK: UICollectionViewDelegate UICollectionViewDataSource
extension SRCoinsPackClaimListView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SRCoinsPackClaimListCell
cell.model = self.dataArr[indexPath.row]
cell.clickClaimButton = { [weak self] id in
self?.clickClaimButton?(id)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataArr.count
}
}

View File

@ -2,7 +2,7 @@
// SRCategoryModel.swift
// SynthReel
//
// Created by on 2025/11/14.
// Created by on 2025/11/14.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeModuleItem.swift
// SynthReel
//
// Created by on 2025/11/14.
// Created by on 2025/11/14.
// Copyright © 2025 SR. All rights reserved.
//
@ -22,6 +22,7 @@ class SRHomeModuleItem: NSObject, SmartCodable {
case bingeWorthy = "week_highest_recommend"
case viralHits = "highest_payment_hot_video"
case premiereNow = "new_recommand"
case catetoryList = "category_navigation"
}

View File

@ -2,7 +2,7 @@
// SRHomeBannerCell.swift
// SynthReel
//
// Created by on 2025/11/15.
// Created by on 2025/11/15.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeBannerMiniCell.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeBannerView.swift
// SynthReel
//
// Created by on 2025/11/15.
// Created by on 2025/11/15.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeBingeWorthyCell.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeBingeWorthyView.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeChildCell.swift
// SynthReel
//
// Created by on 2025/11/14.
// Created by on 2025/11/14.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -0,0 +1,50 @@
//
// SRHomeCoinsPackButton.swift
// SynthReel
//
// Created by on 2025/12/9.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRHomeCoinsPackButton: UIControl {
private lazy var bgImageView = UIImageView(image: UIImage(named: "浮窗"))
private lazy var textBgImageView = UIImageView(image: UIImage(named: "Rectangle 1098"))
private lazy var textLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 10, weight: .init(900)).withBoldItalic()
label.textColor = .white
label.text = "synthreel_daily_coins".localized
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(bgImageView)
addSubview(textBgImageView)
textBgImageView.addSubview(textLabel)
bgImageView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
}
textBgImageView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalToSuperview()
make.top.equalToSuperview().offset(50)
}
textLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.centerX.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -2,7 +2,7 @@
// SRHomeHeaderView.swift
// SynthReel
//
// Created by on 2025/11/15.
// Created by on 2025/11/15.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -0,0 +1,143 @@
//
// SRHomeHistoryBottomView.swift
// SynthReel
//
// Created by on 2025/11/27.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRHomeHistoryBottomView: UIView {
var model: SRShortModel? {
didSet {
coverImageView.sr_setImage(model?.image_url)
titleLabel.text = model?.name
let text = String(format: "EP. %@ / EP. %d", model?.current_episode ?? "0", model?.episode_total ?? 0)
setEpisodeText(text)
}
}
private lazy var coverImageView: SRImageView = {
let imageView = SRImageView()
imageView.layer.cornerRadius = 21
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var bgView: UIImageView = {
let view = UIImageView()
view.image = .recordBottomBg
return view
}()
private lazy var playView: UIImageView = {
let imageView = UIImageView(image: .recordBottomArrow)
imageView.setContentHuggingPriority(.required, for: .horizontal)
imageView.setContentCompressionResistancePriority(.required, for: .horizontal)
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .medium)
label.textColor = .white
return label
}()
private lazy var epLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 10, weight: .regular)
label.textColor = .white
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
fa_setupLayout()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleBg))
addGestureRecognizer(tap)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func handleBg() {
let vc = SRDetailPlayerViewController()
vc.shortId = self.model?.short_play_id
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}
func setEpisodeText(_ text: String) {
let full = text as NSString
let parts = text.components(separatedBy: " / ")
guard parts.count == 2 else {
epLabel.text = text
return
}
let front = parts[0] + " "
let frontLen = (front as NSString).length
let fullLen = full.length
let attr = NSMutableAttributedString(string: text)
//
attr.yy_setColor(.srBlue, range: NSRange(location: 0, length: frontLen))
attr.yy_setFont(.systemFont(ofSize: 12), range: NSRange(location: 0, length: frontLen))
//
attr.yy_setColor(UIColor.white, range: NSRange(location: frontLen, length: fullLen - frontLen))
attr.yy_setFont(.systemFont(ofSize: 12), range: NSRange(location: frontLen, length: fullLen - frontLen))
epLabel.attributedText = attr
}
}
extension SRHomeHistoryBottomView {
private func fa_setupLayout() {
addSubview(bgView)
addSubview(coverImageView)
bgView.addSubview(playView)
bgView.addSubview(titleLabel)
bgView.addSubview(epLabel)
bgView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalToSuperview().offset(1)
make.height.equalTo(57)
}
coverImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(25)
make.width.equalTo(42)
make.height.equalTo(42)
}
playView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.right.equalToSuperview().offset(-28)
}
titleLabel.snp.makeConstraints { make in
make.left.equalTo(coverImageView.snp.right).offset(10)
make.top.equalToSuperview().offset(12)
make.right.lessThanOrEqualTo(playView.snp.left).offset(-10)
}
epLabel.snp.makeConstraints { make in
make.left.equalTo(titleLabel)
make.bottom.equalToSuperview().offset(-12)
}
}
}

View File

@ -2,7 +2,7 @@
// SRHomeHotView.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeMenuCell.swift
// SynthReel
//
// Created by on 2025/11/14.
// Created by on 2025/11/14.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeModuleView.swift
// SynthReel
//
// Created by on 2025/11/15.
// Created by on 2025/11/15.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomePremiereNowView.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeTopChartsContentView.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeTopChartsView.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeViralHitsCell.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeViralHitsView.swift
// SynthReel
//
// Created by on 2025/11/17.
// Created by on 2025/11/17.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeYouLikeCell.swift
// SynthReel
//
// Created by on 2025/11/15.
// Created by on 2025/11/15.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHomeYouLikeView.swift
// SynthReel
//
// Created by on 2025/11/15.
// Created by on 2025/11/15.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHotSearchCell.swift
// SynthReel
//
// Created by on 2025/11/18.
// Created by on 2025/11/18.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRHotSearchView.swift
// SynthReel
//
// Created by on 2025/11/18.
// Created by on 2025/11/18.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRSearchHomeView.swift
// SynthReel
//
// Created by on 2025/11/18.
// Created by on 2025/11/18.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRSearchRecordCell.swift
// SynthReel
//
// Created by on 2025/11/18.
// Created by on 2025/11/18.
// Copyright © 2025 SR. All rights reserved.
//

View File

@ -2,7 +2,7 @@
// SRSearchRecordView.swift
// SynthReel
//
// Created by on 2025/11/18.
// Created by on 2025/11/18.
// Copyright © 2025 SR. All rights reserved.
//

Some files were not shown because too many files have changed in this diff Show More