广告bug修复,1.1.0发布

This commit is contained in:
zjx 2025-07-17 19:43:53 +08:00
parent d341359a51
commit 93a33a226c
35 changed files with 904 additions and 48 deletions

View File

@ -140,6 +140,9 @@
BF3339172E2624B800B10F76 /* VPAppOpenAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3339162E2624AF00B10F76 /* VPAppOpenAdManager.swift */; };
BF33391B2E26339300B10F76 /* VPAppOpenAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF33391A2E26339300B10F76 /* VPAppOpenAdViewController.swift */; };
BF33391F2E264BE200B10F76 /* VPBannerAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF33391E2E264BE200B10F76 /* VPBannerAdManager.swift */; };
BF3339212E28860A00B10F76 /* VPDiscountsVipAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3339202E28860900B10F76 /* VPDiscountsVipAlertView.swift */; };
BF3339232E289B9400B10F76 /* VPApplovinBannerAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3339222E289B9400B10F76 /* VPApplovinBannerAd.swift */; };
BF3339252E28D60700B10F76 /* VPDiscountsVipCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3339242E28D60700B10F76 /* VPDiscountsVipCell.swift */; };
BF5E75AF2DE4632200DE9DFE /* VPAboutUsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E75AE2DE4632200DE9DFE /* VPAboutUsViewController.swift */; };
BF5E75B12DE4656600DE9DFE /* VPCampaignWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E75B02DE4656600DE9DFE /* VPCampaignWebViewController.swift */; };
BF5E75B32DE465EC00DE9DFE /* Dictionary+SPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5E75B22DE465EA00DE9DFE /* Dictionary+SPAdd.swift */; };
@ -432,6 +435,9 @@
BF3339162E2624AF00B10F76 /* VPAppOpenAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPAppOpenAdManager.swift; sourceTree = "<group>"; };
BF33391A2E26339300B10F76 /* VPAppOpenAdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPAppOpenAdViewController.swift; sourceTree = "<group>"; };
BF33391E2E264BE200B10F76 /* VPBannerAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPBannerAdManager.swift; sourceTree = "<group>"; };
BF3339202E28860900B10F76 /* VPDiscountsVipAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPDiscountsVipAlertView.swift; sourceTree = "<group>"; };
BF3339222E289B9400B10F76 /* VPApplovinBannerAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPApplovinBannerAd.swift; sourceTree = "<group>"; };
BF3339242E28D60700B10F76 /* VPDiscountsVipCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPDiscountsVipCell.swift; sourceTree = "<group>"; };
BF5E75AE2DE4632200DE9DFE /* VPAboutUsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPAboutUsViewController.swift; sourceTree = "<group>"; };
BF5E75B02DE4656600DE9DFE /* VPCampaignWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPCampaignWebViewController.swift; sourceTree = "<group>"; };
BF5E75B22DE465EA00DE9DFE /* Dictionary+SPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+SPAdd.swift"; sourceTree = "<group>"; };
@ -1242,6 +1248,7 @@
isa = PBXGroup;
children = (
BF33391E2E264BE200B10F76 /* VPBannerAdManager.swift */,
BF3339222E289B9400B10F76 /* VPApplovinBannerAd.swift */,
);
path = Banner;
sourceTree = "<group>";
@ -1407,6 +1414,8 @@
BFF5B2362DF013410044227A /* VPAlertWindowManager.swift */,
BFCCE13F2E000ACE00EDE165 /* VPAnpsAlertView.swift */,
BF692ABE2E06934D00A5C2DA /* VPVersionUpdateAlertView.swift */,
BF3339202E28860900B10F76 /* VPDiscountsVipAlertView.swift */,
BF3339242E28D60700B10F76 /* VPDiscountsVipCell.swift */,
);
path = Alert;
sourceTree = "<group>";
@ -1622,6 +1631,7 @@
BF0FA7B82DE44FCC00C9E5F2 /* VPWebViewController.swift in Sources */,
BF0FA72A2DDC922F00C9E5F2 /* VPHomeRecommandCell.swift in Sources */,
BF0FA7322DDEBD6400C9E5F2 /* AppDelegate+Config.swift in Sources */,
BF3339232E289B9400B10F76 /* VPApplovinBannerAd.swift in Sources */,
BF5E75D52DE56B2000DE9DFE /* VPHomeCagetoryRecommandContentCell.swift in Sources */,
BF3339042E249F4B00B10F76 /* VPAdManager.swift in Sources */,
BF3339072E24A2DC00B10F76 /* VPRewardedAdManager.swift in Sources */,
@ -1663,6 +1673,7 @@
BF0FA7A32DE1AB1800C9E5F2 /* VPWatchHistoryCell.swift in Sources */,
BFF5B24C2DF052E80044227A /* VPLoginManager+fb.swift in Sources */,
BF0FA76D2DE053C100C9E5F2 /* VPScrollView.swift in Sources */,
BF3339212E28860A00B10F76 /* VPDiscountsVipAlertView.swift in Sources */,
BFF5B2202DEEE2C50044227A /* VPLoginContentView.swift in Sources */,
1B056E5D2DDACD8E007EE38D /* UIFont+VPAdd.swift in Sources */,
BF0FA7282DDC91F800C9E5F2 /* VPCollectionViewCell.swift in Sources */,
@ -1796,6 +1807,7 @@
BF0FA7772DE0735800C9E5F2 /* VPSearchInputView.swift in Sources */,
BFF5AFB62DE803A30044227A /* VPVipPrivilegeItemView.swift in Sources */,
BFC676C22E13EBED006659E5 /* VPAdPlayerControlView.swift in Sources */,
BF3339252E28D60700B10F76 /* VPDiscountsVipCell.swift in Sources */,
BFF5B2542DF132540044227A /* VPConsumptionRecordsModel.swift in Sources */,
BFF5AFB82DE832580044227A /* VPVipBuyCell.swift in Sources */,
BFF5B22E2DEFEEAF0044227A /* VPDeleteAccountContentView.swift in Sources */,

View File

@ -337,7 +337,32 @@ extension UIColor {
static func colorA3F0DE(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xA3F0DE, alpha: alpha)
}
static func color101010(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0x101010, alpha: alpha)
}
static func color95ECD4(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0x95ECD4, alpha: alpha)
}
static func colorF2FFF6(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xF2FFF6, alpha: alpha)
}
static func colorC5F8FB(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xC5F8FB, alpha: alpha)
}
static func colorC9DEFF(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0xC9DEFF, alpha: alpha)
}
static func color78B3B3(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0x78B3B3, alpha: alpha)
}
static func color7698CF(alpha: CGFloat = 1) -> UIColor {
return UIColor(rgb: 0x7698CF, alpha: alpha)
}
}

View File

@ -25,6 +25,21 @@ class VPWalletAPI {
}
}
///
static func requestDiscountPayTemplate(shortPlayId: String, videoId: String, isLoding: Bool = false, completer: ((_ model: VPPayTemplateModel?) -> Void)?) {
var param = VPNetworkParameters(path: "/paySettingsDiscount")
param.method = .get
param.isLoding = isLoding
param.parameters = [
"short_play_id" : shortPlayId,
"short_play_video_id" : videoId
]
VPNetwork.request(parameters: param) { (response: VPNetworkResponse<VPPayTemplateModel>) in
completer?(response.data)
}
}
///
static func requestCoinUnlockVideo(shortPlayId: String, videoId: String, completer: ((_ model: VPVideoUnlockModel?) -> Void)?) {

View File

@ -130,7 +130,9 @@ extension VPMeViewController {
guard VPRewardedAdManager.manager.isReady else { return }
needShowRewardedAd = false
VPRewardedAdManager.manager.showAd()
let manager = VPRewardedAdManager.manager
manager.statScene = .me
manager.showAd()
}

View File

@ -151,10 +151,10 @@ class VPDetailPlayerViewController: VPVideoPlayerViewController {
override func handleBack() {
super.handleBack()
if VPLoginManager.manager.userInfo?.user_level == .ad {
if VPRewardedAdManager.manager.isReady {
VPRewardedAdManager.manager.showAd()
}
if VPLoginManager.manager.userInfo?.user_level == .ad, VPRewardedAdManager.manager.isReady {
VPRewardedAdManager.manager.videoInfo = self.viewModel.currentPlayer?.videoInfo
VPRewardedAdManager.manager.statScene = .detail
VPRewardedAdManager.manager.showAd()
}
}
@ -283,6 +283,7 @@ extension VPDetailPlayerViewController {
private func onRecommandView() {
let view = VPDetailRecommandView()
view.currentVideoInfo = self.viewModel.currentPlayer?.videoInfo
view.dataArr = recommandList
view.clickCloseButton = { [weak self] in
self?.handleBack()

View File

@ -21,6 +21,7 @@ class VPVideoAdModel: VPModel, SmartCodable {
var shortPlayInfo: VPShortModel?
var revolution: VPShortModel.VideoRevolution?
///APP
var site_info: VPVideoAdSiteInfoModel?
}

View File

@ -25,6 +25,14 @@ class VPDetailRecommandView: HWPanModalContentView {
}
}
var currentVideoInfo: VPVideoInfoModel? {
didSet {
if VPLoginManager.manager.userInfo?.user_level == .ad {
bannerAd.videoInfo = currentVideoInfo
}
}
}
var clickCloseButton: (() -> Void)?
var clickLookButton: ((_ model: VPShortModel) -> Void)?

View File

@ -13,12 +13,14 @@ class VPPlayerRechargeView: HWPanModalContentView {
didSet {
vipView.shortPlayId = shortPlayId
coinsView.shortPlayId = shortPlayId
requestDiscountRechargeData()
}
}
var videoId: String? {
didSet {
vipView.videoId = videoId
coinsView.videoId = videoId
requestDiscountRechargeData()
}
}
@ -77,6 +79,8 @@ class VPPlayerRechargeView: HWPanModalContentView {
var buyFinishBlock: (() -> Void)?
var vipBuyFinishBlock: (() -> Void)?
private var discountPayTemplateModel: VPPayTemplateModel?
//MARK: UI
private lazy var bgView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "bg_image_01"))
@ -213,6 +217,7 @@ class VPPlayerRechargeView: HWPanModalContentView {
updateCoin()
// requestRechargeData()
}
@ -222,8 +227,13 @@ class VPPlayerRechargeView: HWPanModalContentView {
@objc private func handleCloseButton() {
// let payTemplateModel = self.discountPayTemplateModel
self.dismiss(animated: true) {
// if let payTemplateModel = payTemplateModel, payTemplateModel.list_sub_vip?.isEmpty == false {
// let alert = VPDiscountsVipAlertView(payTemplateModel: payTemplateModel)
// alert.show()
// }
}
}
@ -381,4 +391,19 @@ extension VPPlayerRechargeView {
}
///
private func requestDiscountRechargeData() {
// guard let shortPlayId = self.shortPlayId else { return }
// guard let videoId = self.videoId else { return }
//
// VPWalletAPI.requestDiscountPayTemplate(shortPlayId: shortPlayId, videoId: videoId) { [weak self] model in
// guard let self = self else { return }
// if let model = model {
// self.discountPayTemplateModel = model
// }
// }
}
}

View File

@ -139,10 +139,10 @@ extension VPVideoPlayViewModel {
break
case .success:
videoInfo.is_lock = false
finish?()
VPLoginManager.manager.updateUserInfo(completer: nil)
VPLoginManager.manager.updateUserInfo {
videoInfo.is_lock = false
finish?()
}
default: break

View File

@ -31,6 +31,22 @@ class VPPayTemplateItem: VPModel, SmartCodable {
return "year_short_type".localized
}
}
func getTextV2() -> String {
switch self {
case .week:
return "veloria_week_text".localized
case .month:
return "veloria_month_text".localized
case .quarter:
return "veloria_quarter_text".localized
case .year:
return "veloria_year_text".localized
}
}
}
enum SizeType: String, SmartCaseDefaultable {

View File

@ -79,6 +79,8 @@ class VPAppOpenAdManager: NSObject {
private var timeOutTimer: Timer?
private var adsDate: Date?
deinit {
NotificationCenter.default.removeObserver(self)
}
@ -93,7 +95,7 @@ class VPAppOpenAdManager: NSObject {
if self.appOpenAd?.isReady == true {
self.showAd()
} else {
self.timeOutTimer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(handleTimeOutTimer), userInfo: nil, repeats: false)
self.timeOutTimer = Timer.scheduledTimer(timeInterval: 30, target: YYWeakProxy(target: self), selector: #selector(handleTimeOutTimer), userInfo: nil, repeats: false)
self.needAutoShow = true
self.loadAd()
@ -122,6 +124,7 @@ class VPAppOpenAdManager: NSObject {
cleanTimer()
let error = NSError(domain: "time-out", code: -1)
requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
self.delegate?.appOpenAdManager?(adManager: self, didOtherFail: error)
}
@ -134,6 +137,7 @@ class VPAppOpenAdManager: NSObject {
appOpenAd = nil
delegate = nil
isShowing = false
self.adsDate = nil
}
}
@ -152,6 +156,7 @@ extension VPAppOpenAdManager: VPAppOpenAdDelegate {
}
///广
func appOpenAdDidLoadFinish(ad: VPAppOpenAd) {
cleanTimer()
self.isLoading = false
self.delegate?.appOpenAdManagerDidLoadFinish?(adManager: self)
@ -163,13 +168,13 @@ extension VPAppOpenAdManager: VPAppOpenAdDelegate {
}
///广
func appOpenAd(ad: VPAppOpenAd, didDisplayFail error: Error) {
cleanTimer()
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)
}
@ -192,8 +197,17 @@ 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

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

@ -10,20 +10,52 @@ import UIKit
import AppLovinSDK
#endif
class VPBannerAdManager: NSObject {
@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 {
let adUnitID = VPAdManager.applovin_bannerAdUnitID
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(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
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)
}
@ -32,56 +64,61 @@ class VPBannerAdManager: NSObject {
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil)
self.adView.loadAd()
bannerAd.loadAd()
}
}
//MARK: -------------- MAAdViewAdDelegate --------------
extension VPBannerAdManager: MAAdViewAdDelegate {
func didExpand(_ ad: MAAd) {
}
//MARK: -------------- VPBannerAdDelegate --------------
extension VPBannerAdManager: VPBannerAdDelegate {
func didCollapse(_ ad: MAAd) {
///广
func bannerAd(bannerAd: VPBannerAd, didLoadFail error: Error) {
requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
}
func didLoad(_ ad: MAAd) {
///广
func bannerAdDidLoadFinish(bannerAd: VPBannerAd) {
adsDate = Date()
requestStatAd(type: "start", errorMsg: nil)
}
func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
requestStatAd(type: "load_failed", errorMsg: error.message)
}
func didDisplay(_ ad: MAAd) {
///广
func bannerAdDidShow(bannerAd: VPBannerAd) {
}
func didHide(_ ad: MAAd) {
///广
func bannerAdDidDismiss(bannerAd: VPBannerAd) {
requestStatAd(type: "close", errorMsg: nil)
}
func didClick(_ ad: MAAd) {
///广
func bannerAdDidClick(ad: VPBannerAd) {
requestStatAd(type: "click", errorMsg: nil)
}
func didFail(toDisplay ad: MAAd, withError error: MAError) {
requestStatAd(type: "show_failed", errorMsg: error.message)
}
}
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 = adUnitID
model.ad_platform_key = .applovin
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)
}

View File

@ -36,9 +36,9 @@ class VPAdManager: NSObject {
//
let initConfig = ALSdkInitializationConfiguration(sdkKey: "XW2aulJv9urKD4MIIFT1xcSCuyTHaDZ9qUbDqygnTLS04GkdX7WMQJviGP5vDRWGsk4OJJIyLGRV3mbLqOWx0W") { builder in
builder.mediationProvider = ALMediationProviderMAX
#if DEBUG
builder.testDeviceAdvertisingIdentifiers = [JXUUID.idfa()]
#endif
//#if DEBUG
// builder.testDeviceAdvertisingIdentifiers = [JXUUID.idfa()]
//#endif
}
ALSdk.shared().initialize(with: initConfig) { sdkConfig in

View File

@ -0,0 +1,300 @@
//
// VPDiscountsVipAlertView.swift
// Veloria
//
// Created by on 2025/7/17.
//
import UIKit
class VPDiscountsVipAlertView: VPBaseAlertView {
private var payTemplateModel: VPPayTemplateModel
private var currentIndex = 0
//MARK: UI
private lazy var bgView: UIView = {
let view = VPGradientView()
view.colors = [UIColor.color95ECD4().cgColor, UIColor.colorF2FFF6().cgColor, UIColor.colorF2FFF6().cgColor]
view.startPoint = .init(x: 0.5, y: 0)
view.endPoint = .init(x: 0.5, y: 1)
view.locations = [0, 0.5, 1]
view.layer.cornerRadius = 18
view.layer.masksToBounds = true
return view
}()
private lazy var closeButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "close_icon_02"), for: .normal)
button.addTarget(self, action: #selector(handleCloseButton), for: .touchUpInside)
return button
}()
private lazy var bgIconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "bg_icon_01"))
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = VPGradientLabel()
label.gradientLayer.colors = [UIColor.color126B4E().cgColor, UIColor.color1A2D22().cgColor]
label.gradientLayer.startPoint = .init(x: 0.5, y: 0)
label.gradientLayer.endPoint = .init(x: 0.5, y: 1)
label.text = "veloria_vip_unlimited_access".localized
label.font = .fontBold(ofSize: 20)
return label
}()
private lazy var titleIconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "alert_title_icon_01"))
return imageView
}()
private lazy var privilegeView1: VPDiscountsVipPrivilegeView = {
let view = VPDiscountsVipPrivilegeView()
view.iconImage = UIImage(named: "privilege_icon_07")
view.title = "veloria_ad_free_experience".localized
return view
}()
private lazy var privilegeView2: VPDiscountsVipPrivilegeView = {
let view = VPDiscountsVipPrivilegeView()
view.iconImage = UIImage(named: "privilege_icon_08")
view.title = "veloria_unlimited_access_to_all_content".localized
return view
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: 241, height: 64)
layout.minimumLineSpacing = 8
layout.minimumInteritemSpacing = 8
return layout
}()
private lazy var collectionView: VPCollectionView = {
let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isScrollEnabled = false
collectionView.register(VPDiscountsVipCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
private lazy var tipLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 10)
label.textColor = .colorB3B3B3()
label.text = "veloria_vip_tip_text_01".localized
return label
}()
private lazy var openVipButton: UIButton = {
let button = UIButton(type: .custom)
button.setBackgroundImage(UIImage(named: "open_button_01"), for: .normal)
button.setTitle("veloria_activate_now".localized, for: .normal)
button.setTitleColor(.color005D48(), for: .normal)
button.titleLabel?.font = .fontMedium(ofSize: 15)
return button
}()
init(payTemplateModel: VPPayTemplateModel) {
self.payTemplateModel = payTemplateModel
super.init(frame: .zero)
vp_setupUI()
UIView.performWithoutAnimation {
self.collectionView.reloadData()
}
self.collectionView.performBatchUpdates(nil) { [weak self] _ in
guard let self = self else { return }
let height = self.collectionView.contentSize.height
self.collectionView.snp.updateConstraints { make in
make.height.equalTo(height)
}
}
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@discardableResult
override func show(in view: UIView? = nil) -> Self {
VPVipAlertView.lastAlertDate = Date()
return super.show(in: VPAppTool.keyWindow)
}
}
extension VPDiscountsVipAlertView {
@objc private func handleCloseButton() {
self.dismiss()
}
}
extension VPDiscountsVipAlertView {
private func vp_setupUI() {
contentView.addSubview(bgView)
contentView.addSubview(closeButton)
bgView.addSubview(bgIconImageView)
bgView.addSubview(titleIconImageView)
bgView.addSubview(titleLabel)
bgView.addSubview(privilegeView1)
bgView.addSubview(privilegeView2)
bgView.addSubview(collectionView)
bgView.addSubview(tipLabel)
bgView.addSubview(openVipButton)
bgView.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.width.equalTo(274)
// make.height.equalTo(355)
}
closeButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(bgView.snp.bottom).offset(20)
make.bottom.equalToSuperview()
}
bgIconImageView.snp.makeConstraints { make in
make.left.top.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(21)
}
titleIconImageView.snp.makeConstraints { make in
make.left.equalTo(titleLabel).offset(-15)
make.top.equalTo(titleLabel).offset(-7)
}
privilegeView1.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(8)
}
privilegeView2.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(privilegeView1.snp.bottom).offset(7)
}
collectionView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(privilegeView2.snp.bottom).offset(14)
make.bottom.equalToSuperview().offset(-97)
make.width.equalTo(collectionViewLayout.itemSize.width)
make.height.equalTo(1)
}
tipLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().offset(-9)
}
openVipButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().offset(-30)
}
}
}
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
extension VPDiscountsVipAlertView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPDiscountsVipCell
cell.item = payTemplateModel.list_sub_vip?[indexPath.row]
if (payTemplateModel.list_sub_vip?.count ?? 0) > 0 {
cell.vp_isSelected = indexPath.row == self.currentIndex
}
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return payTemplateModel.list_sub_vip?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.currentIndex = indexPath.row
self.collectionView.reloadData()
}
}
class VPDiscountsVipPrivilegeView: UIView {
override var intrinsicContentSize: CGSize {
return .init(width: 100, height: 24)
}
var iconImage: UIImage? {
didSet {
iconImageView.image = iconImage
}
}
var title: String? {
didSet {
titleLabel.text = title
}
}
private lazy var iconImageView: UIImageView = {
let imageView = UIImageView()
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
label.textColor = .color000000()
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .colorA3F0DE(alpha: 0.34)
layer.cornerRadius = 12
layer.masksToBounds = true
addSubview(iconImageView)
addSubview(titleLabel)
iconImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(12)
}
titleLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalTo(iconImageView.snp.right).offset(3)
make.right.equalToSuperview().offset(-12)
}
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,171 @@
//
// VPDiscountsVipCell.swift
// Veloria
//
// Created by on 2025/7/17.
//
import UIKit
class VPDiscountsVipCell: VPCollectionViewCell {
var item: VPPayTemplateItem? {
didSet {
unitLabel.text = item?.currency
moneyLabel.text = item?.price
let oldText = NSMutableAttributedString(string: "\(item?.currency ?? "")\(item?.origin_price ?? "")")
oldText.color = .colorFFFFFF(alpha: 0.7)
oldText.strikethroughColor = oldText.color
oldText.strikethroughStyle = .single
oldLabel.attributedText = oldText
tipLabel.text = "veloria_first_##_discount".localizedReplace(text: item?.vip_type_key?.getTextV2() ?? "")
var colors: [CGColor] = []
switch item?.vip_type_key {
case .week:
bgImageView.image = UIImage(named: "vip_alert_bg_week_v2")
colors = [UIColor.color64A3A7().cgColor, UIColor.color416767().cgColor]
tipBgView.colors = [UIColor.colorC5F8FB().cgColor, UIColor.colorC5F8FB(alpha: 0).cgColor]
tipLabel.textColor = .color78B3B3()
default:
bgImageView.image = UIImage(named: "vip_alert_bg_month_v2")
colors = [UIColor.color647DA7().cgColor, UIColor.color414867().cgColor]
tipBgView.colors = [UIColor.colorC9DEFF().cgColor, UIColor.colorC9DEFF(alpha: 0).cgColor]
tipLabel.textColor = .color7698CF()
}
unitLabel.gradientLayer.colors = colors
moneyLabel.gradientLayer.colors = colors
}
}
var vp_isSelected: Bool = false {
didSet {
selectedImageView.isHidden = !vp_isSelected
}
}
private lazy var bgImageView: UIImageView = {
let imageView = UIImageView()
return imageView
}()
private lazy var unitLabel: VPGradientLabel = {
let label = VPGradientLabel()
label.font = .fontRegular(ofSize: 14)
return label
}()
private lazy var moneyLabel: VPGradientLabel = {
let label = VPGradientLabel()
label.font = .fontAaHouDiHei(ofSize: 22)
return label
}()
private lazy var tipBgView: VPGradientView = {
let view = VPGradientView()
view.locations = [0, 1]
view.startPoint = .init(x: 0, y: 0.5)
view.endPoint = .init(x: 1, y: 0.5)
view.addRadius(topLeft: 10, topRight: 0, bottomLeft: 0, bottomRight: 10)
return view
}()
private lazy var tipLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 10)
return label
}()
private lazy var oldBgView: UIView = {
let view = UIView()
view.backgroundColor = .colorBE0069()
view.addRadius(topLeft: 0, topRight: 10, bottomLeft: 10, bottomRight: 0)
return view
}()
private lazy var oldLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 12)
return label
}()
private lazy var selectedImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "selected_icon_01"))
imageView.isHidden = true
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
vp_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension VPDiscountsVipCell {
private func vp_setupUI() {
contentView.addSubview(bgImageView)
bgImageView.addSubview(unitLabel)
bgImageView.addSubview(moneyLabel)
bgImageView.addSubview(tipBgView)
tipBgView.addSubview(tipLabel)
bgImageView.addSubview(oldBgView)
oldBgView.addSubview(oldLabel)
bgImageView.addSubview(selectedImageView)
bgImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
unitLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(13)
make.top.equalToSuperview().offset(16)
}
moneyLabel.snp.makeConstraints { make in
make.left.equalTo(unitLabel.snp.right)
make.bottom.equalTo(unitLabel).offset(4)
}
tipBgView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.bottom.equalToSuperview().offset(-8.5)
make.height.equalTo(15)
}
tipLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(6)
make.right.equalToSuperview().offset(-7)
}
oldBgView.snp.makeConstraints { make in
make.right.equalToSuperview()
make.top.equalToSuperview()
make.height.equalTo(18)
}
oldLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(8)
make.right.equalToSuperview().offset(-8)
}
selectedImageView.snp.makeConstraints { make in
make.right.bottom.equalToSuperview()
}
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "月会员卡片bg@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "月会员卡片bg@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "周会员卡片bg@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "周会员卡片bg@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View File

@ -92,6 +92,10 @@
"veloria_month" = "1 month";
"veloria_quarter" = "1 quarter";
"veloria_year" = "1 year";
"veloria_week_text" = "Week";
"veloria_month_text" = "Month";
"veloria_quarter_text" = "Quarter";
"veloria_year_text" = "Year";
"veloria_days_count_text" = "## days";
"veloria_last_play_episode" = "Last time Episode ##";
"veloria_coin_buy_title" = "Top Up | Indefinitely use";
@ -105,6 +109,11 @@
"veloria_no_short_found" = "No Short Found";
"veloria_no_ads_tip" = "Ad is on the way";
"veloria_watch_ads_to_unlock" = "Watch ads to unlock";
"veloria_vip_unlimited_access" = "VIP Unlimited Access";
"veloria_ad_free_experience" = "Ad-free experience";
"veloria_unlimited_access_to_all_content" = "Unlimited access to all content";
"veloria_activate_now" = "Activate Now";
"veloria_first_##_discount" = "First ## Discount";
"veloria_bonus_count_text" = "+## Bonus";
@ -143,7 +152,7 @@
"veloria_pay_error_3" = "No in-app purchases can be restored";
"veloria_pay_error_4" = "Payment has been cancelled";
"veloria_vip_tip_text_01" = "By subscribing, you agree to our Terms of Service.";
"veloria_store_tips_ios" = "1. Coins are virtual items and cannot be refunded. Use it for this product.<br>2. Gold coins will never expire, the reward coins will expire 24 hours a day.<br>3. Coins will be used first when unlocking episodes. If the amount is insufficient, reward coins will automatically be used.<br>4. The purchase has not been credited, click <Restore> to refresh.<br>5. For other questions, contact us via Me>Feedback.";