广告开发

This commit is contained in:
湖北秦九 2026-02-04 17:25:54 +08:00
parent 040efe1cda
commit 308b4d5d3d
24 changed files with 1445 additions and 42 deletions

View File

@ -44,6 +44,17 @@
035589362F2305D800FAEF4A /* FAAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035589352F2305D800FAEF4A /* FAAdManager.swift */; };
0355893A2F234B3E00FAEF4A /* FAOpenAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035589392F234B3E00FAEF4A /* FAOpenAdManager.swift */; };
0355893C2F234C8300FAEF4A /* FATradPlusOpenAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0355893B2F234C8300FAEF4A /* FATradPlusOpenAd.swift */; };
0355893E2F2464C100FAEF4A /* FAOpenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0355893D2F2464C100FAEF4A /* FAOpenViewController.swift */; };
035589402F24924300FAEF4A /* FARewardedAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0355893F2F24924300FAEF4A /* FARewardedAdManager.swift */; };
035589422F24945600FAEF4A /* FATradPlusRewardedAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035589412F24945600FAEF4A /* FATradPlusRewardedAd.swift */; };
035589452F249CF200FAEF4A /* FABannerAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035589442F249CF200FAEF4A /* FABannerAdManager.swift */; };
035589472F249D9D00FAEF4A /* FATradPlusBannerAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035589462F249D9D00FAEF4A /* FATradPlusBannerAd.swift */; };
035589492F273C5100FAEF4A /* FATradPlusInterstitialAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035589482F273C5100FAEF4A /* FATradPlusInterstitialAd.swift */; };
0355894B2F274A9400FAEF4A /* FARewardedAdInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0355894A2F274A9400FAEF4A /* FARewardedAdInfo.swift */; };
0355894D2F27530800FAEF4A /* FARewardedAdOverview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0355894C2F27530800FAEF4A /* FARewardedAdOverview.swift */; };
035589502F2AEF3700FAEF4A /* FANativeAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0355894F2F2AEF3700FAEF4A /* FANativeAdManager.swift */; };
035589542F2AF1CB00FAEF4A /* FATradPlusNativeAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035589532F2AF1CB00FAEF4A /* FATradPlusNativeAd.swift */; };
035589582F2B353200FAEF4A /* FATradPlusNativeAdView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035589572F2B353200FAEF4A /* FATradPlusNativeAdView.swift */; };
039CE6042EAA2621007B5EED /* AppDelegate+FAAdjust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039CE6032EAA2612007B5EED /* AppDelegate+FAAdjust.swift */; };
039CE6092EAA2F71007B5EED /* FAAdjustStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039CE6082EAA2F62007B5EED /* FAAdjustStateManager.swift */; };
039CE60B2EAA31CB007B5EED /* FAStatAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039CE60A2EAA31CB007B5EED /* FAStatAPI.swift */; };
@ -422,6 +433,17 @@
035589352F2305D800FAEF4A /* FAAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAAdManager.swift; sourceTree = "<group>"; };
035589392F234B3E00FAEF4A /* FAOpenAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAOpenAdManager.swift; sourceTree = "<group>"; };
0355893B2F234C8300FAEF4A /* FATradPlusOpenAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FATradPlusOpenAd.swift; sourceTree = "<group>"; };
0355893D2F2464C100FAEF4A /* FAOpenViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAOpenViewController.swift; sourceTree = "<group>"; };
0355893F2F24924300FAEF4A /* FARewardedAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FARewardedAdManager.swift; sourceTree = "<group>"; };
035589412F24945600FAEF4A /* FATradPlusRewardedAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FATradPlusRewardedAd.swift; sourceTree = "<group>"; };
035589442F249CF200FAEF4A /* FABannerAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FABannerAdManager.swift; sourceTree = "<group>"; };
035589462F249D9D00FAEF4A /* FATradPlusBannerAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FATradPlusBannerAd.swift; sourceTree = "<group>"; };
035589482F273C5100FAEF4A /* FATradPlusInterstitialAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FATradPlusInterstitialAd.swift; sourceTree = "<group>"; };
0355894A2F274A9400FAEF4A /* FARewardedAdInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FARewardedAdInfo.swift; sourceTree = "<group>"; };
0355894C2F27530800FAEF4A /* FARewardedAdOverview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FARewardedAdOverview.swift; sourceTree = "<group>"; };
0355894F2F2AEF3700FAEF4A /* FANativeAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FANativeAdManager.swift; sourceTree = "<group>"; };
035589532F2AF1CB00FAEF4A /* FATradPlusNativeAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FATradPlusNativeAd.swift; sourceTree = "<group>"; };
035589572F2B353200FAEF4A /* FATradPlusNativeAdView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FATradPlusNativeAdView.swift; sourceTree = "<group>"; };
039CE6032EAA2612007B5EED /* AppDelegate+FAAdjust.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+FAAdjust.swift"; sourceTree = "<group>"; };
039CE6082EAA2F62007B5EED /* FAAdjustStateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAAdjustStateManager.swift; sourceTree = "<group>"; };
039CE60A2EAA31CB007B5EED /* FAStatAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAStatAPI.swift; sourceTree = "<group>"; };
@ -786,6 +808,8 @@
035589342F2304B800FAEF4A /* AdManager */ = {
isa = PBXGroup;
children = (
0355894E2F2AEF0900FAEF4A /* Native */,
035589432F249CC500FAEF4A /* Banner */,
035589382F2319EB00FAEF4A /* Open */,
035589372F2319DD00FAEF4A /* Rewarded */,
035589352F2305D800FAEF4A /* FAAdManager.swift */,
@ -796,6 +820,11 @@
035589372F2319DD00FAEF4A /* Rewarded */ = {
isa = PBXGroup;
children = (
0355893F2F24924300FAEF4A /* FARewardedAdManager.swift */,
035589412F24945600FAEF4A /* FATradPlusRewardedAd.swift */,
035589482F273C5100FAEF4A /* FATradPlusInterstitialAd.swift */,
0355894A2F274A9400FAEF4A /* FARewardedAdInfo.swift */,
0355894C2F27530800FAEF4A /* FARewardedAdOverview.swift */,
);
path = Rewarded;
sourceTree = "<group>";
@ -803,12 +832,32 @@
035589382F2319EB00FAEF4A /* Open */ = {
isa = PBXGroup;
children = (
0355893D2F2464C100FAEF4A /* FAOpenViewController.swift */,
035589392F234B3E00FAEF4A /* FAOpenAdManager.swift */,
0355893B2F234C8300FAEF4A /* FATradPlusOpenAd.swift */,
);
path = Open;
sourceTree = "<group>";
};
035589432F249CC500FAEF4A /* Banner */ = {
isa = PBXGroup;
children = (
035589442F249CF200FAEF4A /* FABannerAdManager.swift */,
035589462F249D9D00FAEF4A /* FATradPlusBannerAd.swift */,
);
path = Banner;
sourceTree = "<group>";
};
0355894E2F2AEF0900FAEF4A /* Native */ = {
isa = PBXGroup;
children = (
0355894F2F2AEF3700FAEF4A /* FANativeAdManager.swift */,
035589532F2AF1CB00FAEF4A /* FATradPlusNativeAd.swift */,
035589572F2B353200FAEF4A /* FATradPlusNativeAdView.swift */,
);
path = Native;
sourceTree = "<group>";
};
039CE6072EAA2F37007B5EED /* AdjustStateManager */ = {
isa = PBXGroup;
children = (
@ -2223,6 +2272,7 @@
F31451S3X15B941342J922Q3 /* TConfigCell.swift in Sources */,
F3670DZ2KI89TBO3L9L36P57 /* XLoginView.swift in Sources */,
F3V24670CK2EGR4595CDQ261 /* AWUModalMageCell.swift in Sources */,
035589502F2AEF3700FAEF4A /* FANativeAdManager.swift in Sources */,
F336143N569M8JG811WM9549 /* XHEedbackDetailView.swift in Sources */,
F3F0952W592747Y1H6263E06 /* UCVBbfdebaffdFlowCell.swift in Sources */,
F3SUP8DL68BIIEF1B8Z6U863 /* CFQConfigView.swift in Sources */,
@ -2252,7 +2302,10 @@
03E239652EAA1945004A8CEC /* SceneDelegate.swift in Sources */,
039CE6092EAA2F71007B5EED /* FAAdjustStateManager.swift in Sources */,
F333U95746V7VK13QI9275B3 /* UMenuTransformerCell.swift in Sources */,
035589582F2B353200FAEF4A /* FATradPlusNativeAdView.swift in Sources */,
F3140D182F162CFB003DA73F /* FAPayRetainAlertData.swift in Sources */,
035589472F249D9D00FAEF4A /* FATradPlusBannerAd.swift in Sources */,
035589402F24924300FAEF4A /* FARewardedAdManager.swift in Sources */,
F3Q234J5M18F1Q5G2948P056 /* NPVBoutModalController.swift in Sources */,
F38Y7NBX3M0RE4O142264411 /* EZEFlow.swift in Sources */,
F363P024H4W1T8LN2546W883 /* YBanner.swift in Sources */,
@ -2262,6 +2315,7 @@
031FDEC82EB1F89F00F4CAC7 /* FACoinsPackModel.swift in Sources */,
F31N0S2575E3YC5AOG80WE90 /* TYElyon.swift in Sources */,
F3JX29E3JQ40V23245TS8Z36 /* YRegisterController.swift in Sources */,
035589422F24945600FAEF4A /* FATradPlusRewardedAd.swift in Sources */,
F381171472542RD403O12925 /* QZOast.swift in Sources */,
F33I62G51648130G0N6LN003 /* JOLayoutRyptorView.swift in Sources */,
F39EH05VP4YX3F00P5162031 /* YFddebcdbeeffcebdfCenterView.swift in Sources */,
@ -2329,6 +2383,7 @@
039CE6202EAB114B007B5EED /* FAPayDateModel.swift in Sources */,
03E23A4A2EAA1A4E004A8CEC /* FAConsumptionRecordsViewController.swift in Sources */,
031FDED02EB2167200F4CAC7 /* FACoinsPackBuyView.swift in Sources */,
035589542F2AF1CB00FAEF4A /* FATradPlusNativeAd.swift in Sources */,
03E23A4B2EAA1A4E004A8CEC /* FAMeCoinsView.swift in Sources */,
03E23A4C2EAA1A4E004A8CEC /* FAHomeViewModel.swift in Sources */,
03E23A4D2EAA1A4E004A8CEC /* FAGenresViewController.swift in Sources */,
@ -2339,6 +2394,7 @@
03E23A512EAA1A4E004A8CEC /* FAConsumptionRecordsCell.swift in Sources */,
03E23A522EAA1A4E004A8CEC /* FARankingListHeaderView.swift in Sources */,
03E23A532EAA1A4E004A8CEC /* FAVideoLockView.swift in Sources */,
035589452F249CF200FAEF4A /* FABannerAdManager.swift in Sources */,
03E9A7482EB5D2CB000D1067 /* FALogoutAlert.swift in Sources */,
03E23A542EAA1A4E004A8CEC /* FAHomeViewController.swift in Sources */,
03E23A552EAA1A4E004A8CEC /* FAPopularListViewController.swift in Sources */,
@ -2357,6 +2413,7 @@
03E23A5E2EAA1A4E004A8CEC /* FAHomeMustSeeContentView.swift in Sources */,
03E9A7902EC1B007000D1067 /* FAActivityManager.swift in Sources */,
03E23A5F2EAA1A4E004A8CEC /* FAEpSelectorCell.swift in Sources */,
0355894B2F274A9400FAEF4A /* FARewardedAdInfo.swift in Sources */,
031FDEEC2EB35DF600F4CAC7 /* FACoinsPackAlert.swift in Sources */,
03E23A602EAA1A4E004A8CEC /* FASearchResultView.swift in Sources */,
03E23A612EAA1A4E004A8CEC /* FAHomeMustSeeContentCell.swift in Sources */,
@ -2395,6 +2452,7 @@
03E23A7A2EAA1A4E004A8CEC /* FASearchRecordView.swift in Sources */,
03E23A7B2EAA1A4E004A8CEC /* FACollectViewController.swift in Sources */,
03E23A7C2EAA1A4E004A8CEC /* FAMeCell.swift in Sources */,
0355894D2F27530800FAEF4A /* FARewardedAdOverview.swift in Sources */,
03E9A73A2EB45154000D1067 /* AppDelegate+FAApns.swift in Sources */,
03E23A7D2EAA1A4E004A8CEC /* FAWalletHeaderView.swift in Sources */,
03E23A7E2EAA1A4E004A8CEC /* FAPlayerProgressView.swift in Sources */,
@ -2426,6 +2484,7 @@
F3774560U03XA8151BTQ2Z61 /* HYLLayoutView.swift in Sources */,
F3N85463XSRG9VFK665G9028 /* XKRefreshCell.swift in Sources */,
039CE6282EAB50A9007B5EED /* FAStoreCoinsView.swift in Sources */,
035589492F273C5100FAEF4A /* FATradPlusInterstitialAd.swift in Sources */,
03E9A7402EB49BE6000D1067 /* FAUpdatesAlert.swift in Sources */,
F35K41930D96476I83FHZ0D9 /* YLZFableonAlignmentCell.swift in Sources */,
F3M8439X72Q55JY9G8U502D9 /* DXYFire.swift in Sources */,
@ -2493,6 +2552,7 @@
F395KWCS21F322A36E293006 /* IHAVeloriaButton.swift in Sources */,
F31PD82C37A3078M87873G8T /* FFCheck.swift in Sources */,
F36588YE88493S7167N94531 /* MProgressBoutCell.swift in Sources */,
0355893E2F2464C100FAEF4A /* FAOpenViewController.swift in Sources */,
F35TM2110T9T394C6D0214G9 /* EXYAlignmentHiveView.swift in Sources */,
F32G5ID338260A2W78SS4618 /* TCFableonCell.swift in Sources */,
F31E84U5Y6YW6430Z54X1K40 /* UCHVion.swift in Sources */,

View File

@ -39,7 +39,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
requestFirebase()
requestApns()
//广
FAOpenAdManager.manager.showAdIfAvailable()
//广
FARewardedAdManager.manager.load()
}
func sceneDidDisconnect(_ scene: UIScene) {
@ -99,6 +102,8 @@ extension SceneDelegate {
let hasOpenApp = UserDefaults.standard.object(forKey: kFAHasBeenOpenedAPPDefaultsKey) as? Bool
if hasOpenApp != true {
//广
FAOpenAdManager.manager.openAdVC.view.isHidden = true
let vc = FAAppStartViewController()
vc.openAppBlock = { [weak self] in
guard let self = self else { return }
@ -118,6 +123,7 @@ extension SceneDelegate {
FAAdjustStateManager.manager.isOpenApp = true
window?.rootViewController = FATabBarController()
window?.addSubview(FAOpenAdManager.manager.openAdVC.view)
// window?.makeKeyAndVisible()
}
}

View File

@ -33,6 +33,10 @@ class FAViewController: UIViewController, JXSegmentedListContainerViewListDelega
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
FAOpenAdManager.manager.vcAllowedShowAd = true
}
func handleHeaderRefresh(_ completer: (() -> Void)?) {
completer?()

View File

@ -266,6 +266,39 @@ struct FAAPI {
}
}
///广
static func requestRewardedAdOverview(completer: ((_ model: [FARewardedAdOverview]?) -> Void)?) {
FANetworkManager.manager.request(FABaseURL + "/ad/watchAdOverview",
method: .get,
parameters: nil,
isLoding: false,
isToast: true) { (response: FANetworkManager.Response<[FARewardedAdOverview]>) in
completer?(response.data)
}
}
///广
static func requestAdUnlockVideo(shortPlayId: String, videoId: String, completer: ((_ model: FAVideoUnlockResult?) -> Void)?) {
let parameters = [
"short_play_id" : shortPlayId,
"video_id" : videoId,
"ads_id" : "",
"ads_platform_key" : "",
"trans_id" : "",
"watch_num" : "1"
]
FANetworkManager.manager.request(FABaseURL + "/viewAdsUnlockVideo",
method: .post,
parameters: parameters,
isLoding: true,
isToast: true) { (response: FANetworkManager.Response<FAVideoUnlockResult>) in
completer?(response.data)
}
}
static func requestVersionUpdateData(completer: ((_ model: FAVersionUpdateModel?) -> Void)?) {
FANetworkManager.manager.request(FABaseURL + "/customer/versionControl",

View File

@ -55,6 +55,12 @@ class FAPlayerDetailControlView: JXPlayerListControlView {
override var isCurrent: Bool {
didSet {
playButton.setNeedsUpdateConfiguration()
if isCurrent {
self.bannerAdManager.loadAd()
} else {
self.bannerAdManager.cleanAd()
}
self.updateBannerAdLayout()
}
}
@ -102,6 +108,7 @@ class FAPlayerDetailControlView: JXPlayerListControlView {
let button = UIButton(configuration: config, primaryAction: UIAction(handler: { [weak self] _ in
self?.fa_viewModel?.userSwitchPlayAndPause()
}))
button.isUserInteractionEnabled = false
button.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
@ -147,7 +154,17 @@ class FAPlayerDetailControlView: JXPlayerListControlView {
return button
}()
private lazy var bannerAdView: UIView = {
let view = UIView()
return view
}()
private lazy var bannerAdManager: FABannerAdManager = {
let manager = FABannerAdManager()
manager.contentView = self.bannerAdView
manager.delegate = self
return manager
}()
deinit {
NotificationCenter.default.removeObserver(self)
@ -198,6 +215,25 @@ class FAPlayerDetailControlView: JXPlayerListControlView {
collectButton.isSelected = state
}
private func updateBannerAdLayout() {
self.bannerAdView.isHidden = !self.bannerAdManager.isAdAvailable
if self.bannerAdManager.isAdAvailable {
epButton.view.snp.updateConstraints { make in
make.bottom.equalToSuperview().offset(-(UIScreen.safeBottom + 10 + 50 + 10))
}
} else {
epButton.view.snp.updateConstraints { make in
make.bottom.equalToSuperview().offset(-(UIScreen.safeBottom + 10))
}
}
}
override func singleTapEvent() {
super.singleTapEvent()
self.fa_viewModel?.userSwitchPlayAndPause()
}
}
extension FAPlayerDetailControlView {
@ -210,7 +246,7 @@ extension FAPlayerDetailControlView {
addSubview(shortNameLabel)
addSubview(playButton)
addSubview(collectButton)
addSubview(bannerAdView)
epButton.view.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
@ -246,8 +282,20 @@ extension FAPlayerDetailControlView {
make.height.equalTo(44)
}
bannerAdView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.left.equalToSuperview().offset(16)
make.bottom.equalToSuperview().offset(-(UIScreen.safeBottom + 10))
make.height.equalTo(50)
}
}
}
extension FAPlayerDetailControlView: FABannerAdManagerDelegate {
func fa_bannerAdManagerDidLoadFinish(manager: FABannerAdManager) {
self.updateBannerAdLayout()
}
}

View File

@ -8,6 +8,7 @@
import UIKit
import JXPlayer
import FDFullscreenPopGesture
import SnapKit
class FAPlayerDetailViewController: JXPlayerListViewController {
@ -30,22 +31,45 @@ class FAPlayerDetailViewController: JXPlayerListViewController {
return self.viewModel as! FAShortDetailViewModel
}
private var epText: String? {
didSet {
returnButton.setNeedsUpdateConfiguration()
}
}
private lazy var returnButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "Frame 3011"), for: .normal)
button.addAction(UIAction(handler: { [weak self] _ in
var configuration = UIButton.Configuration.plain()
configuration.image = UIImage(named: "Frame 3011")
configuration.imagePadding = 4
configuration.contentInsets = .init(top: 0, leading: 10, bottom: 0, trailing: 10)
let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in
self?.handleBackButton()
}), for: .touchUpInside)
}))
button.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
var newConfiguration = button.configuration
if let text = self.epText {
newConfiguration?.attributedTitle = AttributedString(text, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 18, weight: .regular),
.foregroundColor : UIColor.white
]))
} else {
newConfiguration?.attributedTitle = nil
}
button.configuration = newConfiguration
}
return button
}()
private lazy var epLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 18, weight: .regular)
label.textColor = .init(named: .color_FFFFFF)
return label
private(set) lazy var nativeAdContentView: UIView = {
let view = UIView()
view.isHidden = true
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
@ -59,6 +83,8 @@ class FAPlayerDetailViewController: JXPlayerListViewController {
requestDetailList()
self.fa_viewModel.loadNativeAd()
fa_setupLayout()
self.fa_viewModel.requestRecommandData()
@ -71,6 +97,7 @@ class FAPlayerDetailViewController: JXPlayerListViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
FAOpenAdManager.manager.vcAllowedShowAd = false
}
override func viewDidDisappear(_ animated: Bool) {
@ -142,19 +169,21 @@ class FAPlayerDetailViewController: JXPlayerListViewController {
extension FAPlayerDetailViewController {
private func fa_setupLayout() {
view.addSubview(nativeAdContentView)
view.addSubview(returnButton)
view.addSubview(epLabel)
nativeAdContentView.snp.remakeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(self.fa_viewModel.nativeAdManager.contentSize.width)
make.height.equalTo(self.fa_viewModel.nativeAdManager.contentSize.height)
}
returnButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.top.equalToSuperview().offset(UIScreen.safeTop)
make.left.equalToSuperview().offset(6)
make.top.equalTo(view.safeAreaLayoutGuide)
make.height.equalTo(44)
}
epLabel.snp.makeConstraints { make in
make.centerY.equalTo(returnButton)
make.left.equalTo(returnButton.snp.right).offset(4)
}
}
}
@ -185,7 +214,7 @@ extension FAPlayerDetailViewController: JXPlayerListViewControllerDelegate, JXPl
func jx_playerListViewController(_ viewController: JXPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath) {
let model = self.fa_viewModel.dataArr[indexPath.section].episodeList?[indexPath.row]
epLabel.text = "Ep.\(model?.episode ?? "")"
self.epText = "Ep.\(model?.episode ?? "")"
}
func jx_shouldAutoScrollNextEpisode(_ viewController: JXPlayerListViewController) -> Bool {

View File

@ -8,6 +8,7 @@
import SwiftUI
import JXPlayer
import YYText
import SnapKit
//@MainActor
class FAShortDetailViewModel: JXPlayerListViewModel, ObservableObject {
@ -45,6 +46,21 @@ class FAShortDetailViewModel: JXPlayerListViewModel, ObservableObject {
private var payDataRequest: FAPayDataRequest?
override var isPlaying: Bool {
didSet {
if !self.isPlaying {
self.showNativeAd()
} else {
self.dismissNativeAd()
}
}
}
private(set) lazy var nativeAdManager: FANativeAdManager = {
let manager = FANativeAdManager()
return manager
}()
func requestDetailData(indexPath: IndexPath? = nil, completer: ((_ code: Int) -> Void)?) {
isShowRecommand = false
@ -236,12 +252,14 @@ extension FAShortDetailViewModel {
private func _showPayRetainAlert(_ videoInfo: FAVideoInfoModel) {
payDataRequest = FAPayDataRequest()
payDataRequest?.requestPayRetainInfo { [weak self] model in
guard let self = self else { return }
guard let model = model else { return }
guard let model = model else {
self.showRewardedAd()
return
}
let view = FAPayRetainAlert()
view.model = model
@ -250,10 +268,63 @@ extension FAShortDetailViewModel {
guard let self = self else { return }
self.requestDetailData(indexPath: self.currentIndexPath, completer: nil)
}
view.didDismissHandle = { [weak self] in
guard let self = self else { return }
self.showRewardedAd()
}
view.show(in: FATool.keyWindow)
}
}
///广
private func showRewardedAd() {
let manager = FARewardedAdManager.manager
if manager.isAdAvailable {
manager.delegate = self
manager.show()
}
}
///广
func loadNativeAd() {
self.nativeAdManager.load()
}
///广
private func showNativeAd() {
guard let view = (self.playerListVC as? FAPlayerDetailViewController)?.nativeAdContentView else { return }
guard self.nativeAdManager.isAdAvailable else { return }
guard view.isHidden else { return }
view.isHidden = false
self.nativeAdManager.contentView = view
self.nativeAdManager.show()
}
private func dismissNativeAd() {
guard let view = (self.playerListVC as? FAPlayerDetailViewController)?.nativeAdContentView else { return }
view.isHidden = true
}
}
//MARK: FARewardedAdManagerDelegate
extension FAShortDetailViewModel: FARewardedAdManagerDelegate {
func fa_rewardedAdManagerDidDismiss(adManager: FARewardedAdManager) {
guard let videoInfo = self.currentCell?.model as? FAVideoInfoModel else { return }
guard let shortPlayId = videoInfo.short_play_id else { return }
guard let videoId = videoInfo.short_play_video_id else { return }
FAAPI.requestAdUnlockVideo(shortPlayId: shortPlayId, videoId: videoId) { [weak self] model in
guard let self = self else { return }
guard model?.status == .success else { return }
self.requestDetailData(indexPath: self.currentIndexPath, completer: nil)
}
}
}
extension FAShortDetailViewModel {

View File

@ -61,6 +61,11 @@ class FARecommendViewController: JXPlayerListViewController {
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
FAOpenAdManager.manager.vcAllowedShowAd = false
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.viewModel.currentCell?.pause()

View File

@ -0,0 +1,136 @@
//
// FABannerAdManager.swift
// Fableon
//
// Created by on 2026/1/24.
//
import UIKit
@objc protocol FABannerAdManagerDelegate: NSObjectProtocol {
///广
@objc optional func fa_bannerAdManager(manager: FABannerAdManager, didLoadFail error: Error)
///广
@objc optional func fa_bannerAdManagerDidLoadFinish(manager: FABannerAdManager)
///广
@objc optional func fa_bannerAdManagerDidShow(manager: FABannerAdManager)
/// 广
@objc optional func fa_bannerAdManager(manager: FABannerAdManager, didDisplayFail error: Error)
///广
@objc optional func fa_bannerAdManagerDidDismiss(manager: FABannerAdManager)
///广
@objc optional func fa_bannerAdManagerDidClick(manager: FABannerAdManager)
}
protocol FABannerAdDelegate: NSObjectProtocol {
///广
func fa_bannerAd(bannerAd: FABannerAd, didLoadFail error: Error)
///广
func fa_bannerAdDidLoadFinish(bannerAd: FABannerAd)
///广
func fa_bannerAdDidShow(bannerAd: FABannerAd)
///广
func fa_bannerAd(bannerAd: FABannerAd, didDisplayFail error: Error)
///广
func fa_bannerAdDidDismiss(bannerAd: FABannerAd)
///广
func fa_bannerAdDidClick(ad: FABannerAd)
}
protocol FABannerAd: NSObjectProtocol {
var delegate: FABannerAdDelegate? { get set }
var adView: UIView? { get }
var adPlatform: FAAdPlatform { get }
var adType: FAAdType { get }
var adUnitID: String { get }
var isReady: Bool { get set }
func loadAd()
}
class FABannerAdManager: NSObject {
private(set) var bannerAd: FABannerAd? {
didSet {
oldValue?.adView?.removeFromSuperview()
oldValue?.delegate = nil
}
}
weak var delegate: FABannerAdManagerDelegate?
///广
var contentView: UIView? {
didSet {
self.updateLayout()
}
}
var isAdAvailable: Bool {
return bannerAd?.isReady ?? false
}
func loadAd() {
let ad = FATradPlusBannerAd()
ad.delegate = self
self.bannerAd = ad
self.updateLayout()
self.bannerAd?.loadAd()
}
func cleanAd() {
self.bannerAd = nil
}
private func updateLayout() {
if let adView = bannerAd?.adView, let contentView = self.contentView {
contentView.addSubview(adView)
adView.frame = contentView.bounds
adView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
}
//MARK: FABannerAdManager
extension FABannerAdManager: FABannerAdDelegate {
func fa_bannerAd(bannerAd: any FABannerAd, didDisplayFail error: any Error) {
self.delegate?.fa_bannerAdManager?(manager: self, didDisplayFail: error)
}
func fa_bannerAd(bannerAd: any FABannerAd, didLoadFail error: any Error) {
self.delegate?.fa_bannerAdManager?(manager: self, didLoadFail: error)
}
func fa_bannerAdDidLoadFinish(bannerAd: any FABannerAd) {
self.delegate?.fa_bannerAdManagerDidLoadFinish?(manager: self)
}
func fa_bannerAdDidShow(bannerAd: any FABannerAd) {
self.delegate?.fa_bannerAdManagerDidShow?(manager: self)
}
func fa_bannerAdDidDismiss(bannerAd: any FABannerAd) {
self.delegate?.fa_bannerAdManagerDidDismiss?(manager: self)
}
func fa_bannerAdDidClick(ad: any FABannerAd) {
self.delegate?.fa_bannerAdManagerDidClick?(manager: self)
}
}

View File

@ -0,0 +1,86 @@
//
// FATradPlusBannerAd.swift
// Fableon
//
// Created by on 2026/1/24.
//
import UIKit
#if canImport(TradPlusAds)
import TradPlusAds
#endif
class FATradPlusBannerAd: NSObject, FABannerAd {
#if canImport(TradPlusAds)
private lazy var bannerView: TradPlusAdBanner = {
let view = TradPlusAdBanner()
view.delegate = self
view.translatesAutoresizingMaskIntoConstraints = false
// view.adSize = .
return view
}()
#endif
weak var delegate: FABannerAdDelegate?
var adView: UIView? {
#if canImport(TradPlusAds)
return self.bannerView
#else
return nil
#endif
}
var isReady: Bool = false
var adPlatform: FAAdPlatform {
return .tradPlus
}
var adType: FAAdType {
return .banner
}
var adUnitID: String {
return self.adPlatform.bannerUnitId
}
func loadAd() {
self.bannerView.setAdUnitID(self.adUnitID)
self.bannerView.loadAd(withSceneId: nil)
}
}
#if canImport(TradPlusAds)
extension FATradPlusBannerAd: TradPlusADBannerDelegate {
func tpBannerAdLoaded(_ adInfo: [AnyHashable : Any]) {
self.isReady = true
debugLog("+++++++++++广告加载成功")
self.delegate?.fa_bannerAdDidLoadFinish(bannerAd: self)
}
func tpBannerAdLoadFailWithError(_ error: any Error) {
self.delegate?.fa_bannerAd(bannerAd: self, didLoadFail: error)
}
func tpBannerAdImpression(_ adInfo: [AnyHashable : Any]) {
debugLog("+++++++++++广告加载展示成功")
self.delegate?.fa_bannerAdDidShow(bannerAd: self)
}
func tpBannerAdShow(_ adInfo: [AnyHashable : Any], didFailWithError error: any Error) {
self.delegate?.fa_bannerAd(bannerAd: self, didDisplayFail: error)
}
func tpBannerAdClicked(_ adInfo: [AnyHashable : Any]) {
self.delegate?.fa_bannerAdDidClick(ad: self)
}
func viewControllerForPresentingModalView() -> UIViewController? {
return nil
}
}
#endif

View File

@ -6,6 +6,7 @@
//
import UIKit
import SmartCodable
#if canImport(TradPlusAds)
import TradPlusAds
#endif
@ -31,9 +32,16 @@ class FAAdManager: NSObject {
}
enum FAAdType {
case rewarded
case interstitial
case banner
case open
case native
}
enum FAAdPlatform {
case tradPlus
enum FAAdPlatform: String, SmartCaseDefaultable {
case tradPlus = "tradplus"
///广Id
var rewardedUnitId: String {
@ -59,4 +67,21 @@ enum FAAdPlatform {
}
}
///广
var interstitialUnitId: String {
switch self {
case .tradPlus:
return "F8303BAF407F8ED3642B1F8B703D4822"
}
}
///广
var nativeUnitId: String {
switch self {
case .tradPlus:
return "C4463BAD71D16B4B840703EAEBD0D422"
}
}
}

View File

@ -0,0 +1,145 @@
//
// FANativeAdManager.swift
// Fableon
//
// Created by on 2026/1/29.
//
import UIKit
@objc protocol FANativeAdManagerDelegate: NSObjectProtocol {
///广
@objc optional func fa_nativeAdManager(manager: FANativeAdManager, didLoadFail error: Error)
///广
@objc optional func fa_nativeAdManagerDidLoadFinish(manager: FANativeAdManager)
///广
@objc optional func fa_nativeAdManagerDidShow(manager: FANativeAdManager)
/// 广
@objc optional func fa_nativeAdManager(manager: FANativeAdManager, didDisplayFail error: Error)
///广
@objc optional func fa_nativeAdManagerDidDismiss(manager: FANativeAdManager)
///广
@objc optional func fa_nativeAdManagerDidClick(manager: FANativeAdManager)
}
protocol FANativeAdDelegate: NSObjectProtocol {
///广
func fa_nativeAd(nativeAd: FANativeAd, didLoadFail error: Error)
///广
func fa_nativeAdDidLoadFinish(nativeAd: FANativeAd)
///广
func fa_nativeAdDidShow(nativeAd: FANativeAd)
///广
func fa_nativeAd(nativeAd: FANativeAd, didDisplayFail error: Error)
///广
func fa_nativeAdDidDismiss(nativeAd: FANativeAd)
///广
func fa_nativeAdDidClick(nativeAd: FANativeAd)
}
protocol FANativeAd: NSObjectProtocol {
var delegate: FANativeAdDelegate? { get set }
var adView: UIView? { get set }
var adSize: CGSize? { get set }
var adPlatform: FAAdPlatform { get }
var adType: FAAdType { get }
var adUnitID: String { get }
var isReady: Bool { get }
func loadAd()
func show()
}
class FANativeAdManager: NSObject {
weak var delegate: FANativeAdManagerDelegate?
///广
var contentView: UIView? {
didSet {
self.nativeAd?.adView = contentView
}
}
let contentSize: CGSize = .init(width: 300, height: 400)
var isAdAvailable: Bool {
return nativeAd?.isReady ?? false
}
///广
private(set) var isLoadingAd = false
///
var hasShow = false
private var nativeAd: FANativeAd? {
didSet {
oldValue?.delegate = nil
nativeAd?.delegate = self
}
}
func load() {
guard !self.isLoadingAd else { return }
self.isLoadingAd = true
self.nativeAd = FATradPlusNativeAd()
self.nativeAd?.adSize = self.contentSize
self.nativeAd?.adView = contentView
self.nativeAd?.delegate = self
self.nativeAd?.loadAd()
}
func show() {
guard !hasShow else { return }
self.hasShow = true
self.nativeAd?.show()
}
}
extension FANativeAdManager: FANativeAdDelegate {
func fa_nativeAdDidLoadFinish(nativeAd: any FANativeAd) {
self.isLoadingAd = false
self.delegate?.fa_nativeAdManagerDidLoadFinish?(manager: self)
}
func fa_nativeAdDidShow(nativeAd: any FANativeAd) {
self.delegate?.fa_nativeAdManagerDidShow?(manager: self)
}
func fa_nativeAd(nativeAd: any FANativeAd, didLoadFail error: any Error) {
self.isLoadingAd = false
self.delegate?.fa_nativeAdManager?(manager: self, didLoadFail: error)
}
func fa_nativeAd(nativeAd: any FANativeAd, didDisplayFail error: any Error) {
self.delegate?.fa_nativeAdManager?(manager: self, didDisplayFail: error)
}
func fa_nativeAdDidDismiss(nativeAd: any FANativeAd) {
self.delegate?.fa_nativeAdManagerDidDismiss?(manager: self)
}
func fa_nativeAdDidClick(nativeAd: any FANativeAd) {
self.delegate?.fa_nativeAdManagerDidClick?(manager: self)
}
}

View File

@ -0,0 +1,101 @@
//
// FATradPlusNativeAd.swift
// Fableon
//
// Created by on 2026/1/29.
//
import UIKit
#if canImport(TradPlusAds)
import TradPlusAds
#endif
class FATradPlusNativeAd: NSObject, FANativeAd {
#if canImport(TradPlusAds)
private var nativeAd: TradPlusAdNative?
#endif
weak var delegate: FANativeAdDelegate?
var adView: UIView?
var adSize: CGSize?
var adPlatform: FAAdPlatform {
return .tradPlus
}
var adType: FAAdType {
return .native
}
var adUnitID: String {
return adPlatform.nativeUnitId
}
var isReady: Bool {
#if canImport(TradPlusAds)
return self.nativeAd?.isAdReady ?? false
#else
return false
#endif
}
func loadAd() {
#if canImport(TradPlusAds)
guard let adSize = self.adSize else { return }
self.nativeAd = TradPlusAdNative()
self.nativeAd?.setAdUnitID(self.adUnitID)
self.nativeAd?.setTemplateRenderSize(adSize)
self.nativeAd?.delegate = self
self.nativeAd?.loadAd()
#endif
}
func show() {
guard self.isReady else { return }
guard let adView = adView else { return }
// let view = FATradPlusNativeAdView()
//
// let nativeRenderer = TradPlusNativeRenderer()
// nativeRenderer.setTitleLable(view.titleLabel, canClick: true)
// self.nativeAd?.showAD(with: nativeRenderer, subview: adView, sceneId: nil)
self.nativeAd?.showAD(withRenderingViewClass: FATradPlusNativeAdView.self, subview: adView, sceneId: nil)
}
}
//MARK: TradPlusADNativeDelegate
#if canImport(TradPlusAds)
extension FATradPlusNativeAd: TradPlusADNativeDelegate {
func tpNativeAdLoaded(_ adInfo: [AnyHashable : Any]) {
self.delegate?.fa_nativeAdDidLoadFinish(nativeAd: self)
}
func tpNativeAdImpression(_ adInfo: [AnyHashable : Any]) {
self.delegate?.fa_nativeAdDidShow(nativeAd: self)
}
func tpNativeAdLoadFailWithError(_ error: any Error) {
self.delegate?.fa_nativeAd(nativeAd: self, didLoadFail: error)
}
func tpNativeAdShow(_ adInfo: [AnyHashable : Any], didFailWithError error: any Error) {
self.delegate?.fa_nativeAd(nativeAd: self, didDisplayFail: error)
}
func tpNativeAdClicked(_ adInfo: [AnyHashable : Any]) {
self.delegate?.fa_nativeAdDidClick(nativeAd: self)
}
func tpNativeAdClose(_ adInfo: [AnyHashable : Any]) {
self.delegate?.fa_nativeAdDidDismiss(nativeAd: self)
}
}
#endif

View File

@ -0,0 +1,160 @@
//
// FATradPlusNativeAdView.swift
// Fableon
//
// Created by on 2026/1/29.
//
import UIKit
import SnapKit
#if canImport(TradPlusAds)
import TradPlusAds
#endif
class FATradPlusNativeAdView: UIView {
lazy var mainImageView: FAImageView = {
let imageView = FAImageView()
return imageView
}()
lazy var iconImageView: UIImageView = {
let imageView = FAImageView()
imageView.layer.cornerRadius = 5
imageView.layer.masksToBounds = true
return imageView
}()
lazy var titleLabel: UILabel = {
let label = UILabel()
label.textColor = .black
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
lazy var textLabel: UILabel = {
let label = UILabel()
label.textColor = .black
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
lazy var ctaLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.textAlignment = .center
label.numberOfLines = 0
label.layer.cornerRadius = 5
label.layer.masksToBounds = true
label.backgroundColor = .CC_3333
return label
}()
lazy var adChoiceImageView: UIImageView = {
let imageView = UIImageView()
return imageView
}()
lazy var adLabel: UILabel = {
let label = UILabel()
label.textColor = .orange
label.font = .font(ofSize: 10, weight: .regular)
label.text = "AD"
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .white
fa_layoutUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func fa_layoutUI() {
addSubview(mainImageView)
addSubview(iconImageView)
addSubview(titleLabel)
addSubview(textLabel)
addSubview(ctaLabel)
addSubview(adChoiceImageView)
addSubview(adLabel)
mainImageView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
// make.height.equalTo(80)
make.height.equalTo(mainImageView.snp.width).multipliedBy(0.5)
}
iconImageView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(mainImageView.snp.bottom).offset(10)
make.width.height.equalTo(50)
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(iconImageView.snp.bottom).offset(10)
make.centerX.equalToSuperview()
make.right.lessThanOrEqualToSuperview().offset(-10)
}
textLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(10)
make.right.lessThanOrEqualToSuperview().offset(-10)
}
ctaLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(textLabel.snp.bottom).offset(10)
make.right.equalToSuperview().offset(-10)
make.height.equalTo(40)
}
adChoiceImageView.snp.makeConstraints { make in
make.width.height.equalTo(10)
make.right.equalToSuperview().offset(-10)
make.bottom.equalToSuperview().offset(-10)
}
adLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(10)
make.centerY.equalTo(adChoiceImageView)
}
}
}
#if canImport(TradPlusAds)
extension FATradPlusNativeAdView: TradPlusNativeAdRendering {
func nativeTitleTextLabel() -> UILabel! {
return titleLabel
}
func nativeMainTextLabel() -> UILabel! {
return textLabel
}
func nativeIconImageView() -> UIImageView! {
return iconImageView
}
func nativeMainImageView() -> UIImageView! {
return mainImageView
}
func nativeCallToActionTextLabel() -> UILabel! {
return ctaLabel
}
func nativePrivacyInformationIconImageView() -> UIImageView! {
return adChoiceImageView
}
}
#endif

View File

@ -34,6 +34,7 @@ protocol FAOpenAdDelegate: NSObjectProtocol {
protocol FAOpenAd: NSObjectProtocol {
var delegate: FAOpenAdDelegate? { get set }
var adPlatform: FAAdPlatform { get }
var adType: FAAdType { get }
var adUnitID: String { get }
var isReady: Bool { get }
@ -47,6 +48,8 @@ class FAOpenAdManager: NSObject {
weak var delegate: FAOpenAdManagerDelegate?
var openAdVC: FAOpenViewController = FAOpenViewController()
private var appOpenAd: FAOpenAd? {
didSet {
oldValue?.delegate = nil
@ -54,12 +57,20 @@ class FAOpenAdManager: NSObject {
}
}
///广
var vcAllowedShowAd = true
///广
private(set) var isLoadingAd = false
///广
private(set) var isShowingAd = false
/// 广
private var isWaitingToShow = false
private(set) var isWaitingToShow = false
private var timeOutTimer: Timer?
var isAdAvailable: Bool {
return appOpenAd?.isReady ?? false
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@ -71,7 +82,7 @@ class FAOpenAdManager: NSObject {
}
func loadAd() {
if isLoadingAd || isAdAvailable() {
if isLoadingAd || isAdAvailable {
return
}
isLoadingAd = true
@ -81,10 +92,11 @@ class FAOpenAdManager: NSObject {
}
func showAd() {
guard let ad = appOpenAd, isAdAvailable() else {
guard let ad = appOpenAd, isAdAvailable else {
loadAd()
return
}
if self.openAdVC.view.isHidden { return }
isWaitingToShow = false
isShowingAd = true
@ -97,7 +109,7 @@ class FAOpenAdManager: NSObject {
guard !isShowingAd else { return }
// 1. 广
if isAdAvailable() {
if isAdAvailable {
showAd()
return
}
@ -108,18 +120,16 @@ class FAOpenAdManager: NSObject {
loadAd()
}
private func isAdAvailable() -> Bool {
return appOpenAd?.isReady ?? false
}
// MARK: - Timer (广)
private func startTimeoutTimer() {
clearTimer()
// timeOutTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { [weak self] _ in
// Task { @MainActor in
// self?.handleTimeOut()
// }
// }
timeOutTimer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { [weak self] _ in
Task { @MainActor in
self?.handleTimeOut()
}
}
}
@objc private func handleTimeOut() {
@ -150,7 +160,6 @@ extension FAOpenAdManager: FAOpenAdDelegate {
clearTimer()
requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
delegate?.fa_openAdManager?(manager: self, didLoadFail: error)
delegate = nil
}
func fa_openAdDidLoadFinish(ad: FAOpenAd) {
@ -171,7 +180,6 @@ extension FAOpenAdManager: FAOpenAdDelegate {
clearTimer()
requestStatAd(type: "show_failed", errorMsg: error.localizedDescription)
delegate?.fa_openAdManager?(manager: self, didDisplayFail: error)
delegate = nil
}
func fa_openAdDidShow(ad: FAOpenAd) {
@ -183,9 +191,11 @@ extension FAOpenAdManager: FAOpenAdDelegate {
isShowingAd = false
appOpenAd = nil
delegate?.fa_openAdManagerDidDismiss?(manager: self)
//
loadAd()
delegate = nil
// 5
DispatchQueue.main.asyncAfter(deadline: .now() + 10) { [weak self] in
guard let self = self else { return }
self.loadAd()
}
}
func fa_openAdDidClick(ad: FAOpenAd) {
@ -216,6 +226,9 @@ extension FAOpenAdManager {
}
@objc private func didBecomeActiveNotification() {
showAdIfAvailable()
if self.isAdAvailable, self.vcAllowedShowAd {
self.openAdVC.view.isHidden = false
self.showAd()
}
}
}

View File

@ -0,0 +1,57 @@
//
// FAOpenViewController.swift
// Fableon
//
// Created by on 2026/1/24.
//
import UIKit
class FAOpenViewController: UIViewController {
private lazy var lanuchVC: UIViewController? = {
return FATool.getLanuchViewController()
}()
override func viewDidLoad() {
super.viewDidLoad()
if let vc = lanuchVC {
addChild(vc)
view.addSubview(vc.view)
}
FAOpenAdManager.manager.delegate = self
}
}
//MARK: FAOpenAdManagerDelegate
extension FAOpenViewController: FAOpenAdManagerDelegate {
func fa_openAdManagerDidShow(manager: FAOpenAdManager) {
}
func fa_openAdManagerDidDismiss(manager: FAOpenAdManager) {
self.view.isHidden = true
}
func fa_openAdManager(manager: FAOpenAdManager, didLoadFail error: any Error) {
self.view.isHidden = true
}
func fa_openAdManager(manager: FAOpenAdManager, didOtherFail error: any Error) {
self.view.isHidden = true
}
func fa_openAdManager(manager: FAOpenAdManager, didDisplayFail error: any Error) {
self.view.isHidden = true
}
}

View File

@ -21,6 +21,10 @@ class FATradPlusOpenAd: NSObject, FAOpenAd {
var adPlatform: FAAdPlatform {
return .tradPlus
}
var adType: FAAdType {
return .open
}
var adUnitID: String {
return self.adPlatform.openUnitId

View File

@ -0,0 +1,27 @@
//
// FARewardedAdInfo.swift
// Fableon
//
// Created by on 2026/1/26.
//
import UIKit
import SmartCodable
class FARewardedAdData: NSObject, SmartCodable {
required override init() { }
var ad: FARewardedAdInfo?
}
class FARewardedAdInfo: NSObject, SmartCodable {
required override init() { }
var id: String?
var platform_name: String?
var ads_id: String?
var status: String?
var platform_key: FAAdPlatform?
}

View File

@ -0,0 +1,157 @@
//
// FARewardedAdManager.swift
// Fableon
//
// Created by on 2026/1/24.
//
import UIKit
@objc protocol FARewardedAdManagerDelegate: NSObjectProtocol {
///
// @objc optional func rewardedAdManager(adManager: BRRewardedAdManager, userDidEarnReward adInfo: VPAdInfo)
///广
@objc optional func fa_rewardedAdManagerDidLoadFinish(adManager: FARewardedAdManager)
///广
@objc optional func fa_rewardedAdManager(adManager: FARewardedAdManager, didLoadFail error: Error)
///广
@objc optional func fa_rewardedAdManager(adManager: FARewardedAdManager, didDisplayFail error: Error)
///广
@objc optional func fa_rewardedAdManagerDidShow(adManager: FARewardedAdManager)
///广
@objc optional func fa_rewardedAdManagerDidDismiss(adManager: FARewardedAdManager)
///广
@objc optional func fa_rewardedAdManagerDidClick(adManager: FARewardedAdManager)
///
@objc optional func fa_rewardedAdManager(adManager: FARewardedAdManager, didOtherFail error: Error)
}
protocol FARewardedAdDelegate: NSObjectProtocol {
///
// func rewardedAd(ad: BRRewardedAd, userDidEarnReward adInfo: VPAdInfo)
///广
func fa_rewardedAd(ad: FARewardedAd, didLoadFail error: Error)
///广
func fa_rewardedAdDidLoadFinish(ad: FARewardedAd)
///广
func fa_rewardedAd(ad: FARewardedAd, didDisplayFail error: Error)
///广
func fa_rewardedAdDidShow(ad: FARewardedAd)
///广
func fa_rewardedAdDidDismiss(ad: FARewardedAd)
///广
func fa_rewardedAdDidClick(ad: FARewardedAd)
}
protocol FARewardedAd: NSObjectProtocol {
var delegate: FARewardedAdDelegate? { get set }
var adPlatform: FAAdPlatform { get }
var adType: FAAdType { get }
var adUnitID: String { get }
var networkName: String { get set }
var isReady: Bool { get }
func loadAd()
func showAd()
}
class FARewardedAdManager: NSObject {
static let manager = FARewardedAdManager()
weak var delegate: FARewardedAdManagerDelegate?
var rewardedAd: FARewardedAd? {
didSet {
oldValue?.delegate = nil
rewardedAd?.delegate = self
}
}
var isAdAvailable: Bool {
return rewardedAd?.isReady ?? false
}
override init() {
super.init()
}
func load() {
if self.isAdAvailable { return }
// if loadAdType != .rewarded {
// loadAdType = .rewarded
// } else {
// loadAdType = .interstitial
// }
//
// if loadAdType == .rewarded {
// self.rewardedAd = FATradPlusRewardedAd()
// } else {
self.rewardedAd = FATradPlusInterstitialAd()
// }
self.rewardedAd?.loadAd()
// self.requestAdOverview()
}
func show() {
guard self.isAdAvailable else { return }
self.rewardedAd?.showAd()
}
}
//MARK: FARewardedAdDelegate
extension FARewardedAdManager: FARewardedAdDelegate {
func fa_rewardedAd(ad: any FARewardedAd, didLoadFail error: any Error) {
self.rewardedAd = nil
self.delegate?.fa_rewardedAdManager?(adManager: self, didLoadFail: error)
}
func fa_rewardedAd(ad: any FARewardedAd, didDisplayFail error: any Error) {
self.rewardedAd = nil
self.delegate?.fa_rewardedAdManager?(adManager: self, didDisplayFail: error)
}
func fa_rewardedAdDidLoadFinish(ad: any FARewardedAd) {
self.delegate?.fa_rewardedAdManagerDidLoadFinish?(adManager: self)
}
func fa_rewardedAdDidShow(ad: any FARewardedAd) {
self.delegate?.fa_rewardedAdManagerDidShow?(adManager: self)
}
func fa_rewardedAdDidDismiss(ad: any FARewardedAd) {
self.rewardedAd = nil
self.delegate?.fa_rewardedAdManagerDidDismiss?(adManager: self)
//广
self.load()
}
func fa_rewardedAdDidClick(ad: any FARewardedAd) {
self.delegate?.fa_rewardedAdManagerDidClick?(adManager: self)
}
}
extension FARewardedAdManager {
private func requestAdOverview() {
FAAPI.requestRewardedAdOverview { [weak self] list in
guard let self = self else { return }
}
}
}

View File

@ -0,0 +1,28 @@
//
// FARewardedAdOverview.swift
// Fableon
//
// Created by on 2026/1/26.
//
import UIKit
import SmartCodable
class FARewardedAdOverview: NSObject, SmartCodable {
required override init() { }
var id: String?
var platform_key: FAAdPlatform?
var watch_times_limit: Int?
var polling_times: Int?
var ad_type: FARewardedAdWeight?
}
class FARewardedAdWeight: NSObject, SmartCodable {
required override init() { }
var reward: Int?
var interstitial: Int?
}

View File

@ -0,0 +1,91 @@
//
// FATradPlusInterstitialAd.swift
// Fableon
//
// Created by on 2026/1/26.
//
import UIKit
#if canImport(TradPlusAds)
import TradPlusAds
#endif
class FATradPlusInterstitialAd: NSObject, FARewardedAd {
#if canImport(TradPlusAds)
private var interstitialAd: TradPlusAdInterstitial?
#endif
var delegate: FARewardedAdDelegate?
var adPlatform: FAAdPlatform {
return .tradPlus
}
var adType: FAAdType {
return .interstitial
}
var adUnitID: String {
return adPlatform.interstitialUnitId
}
var networkName: String = ""
var isReady: Bool {
#if canImport(TradPlusAds)
return self.interstitialAd?.isAdReady ?? false
#else
return false
#endif
}
func loadAd() {
#if canImport(TradPlusAds)
self.interstitialAd = TradPlusAdInterstitial()
self.interstitialAd?.setAdUnitID(self.adUnitID)
self.interstitialAd?.delegate = self
self.interstitialAd?.loadAd()
#endif
}
func showAd() {
#if canImport(TradPlusAds)
guard self.isReady else { return }
self.interstitialAd?.showAd(withSceneId: nil)
#endif
}
}
#if canImport(TradPlusAds)
//MARK: TradPlusADInterstitialDelegate
extension FATradPlusInterstitialAd: TradPlusADInterstitialDelegate {
func tpInterstitialAdLoaded(_ adInfo: [AnyHashable : Any]) {
self.networkName = (adInfo["adNetworkName"] as? String) ?? "unknown"
self.delegate?.fa_rewardedAdDidLoadFinish(ad: self)
}
func tpInterstitialAdImpression(_ adInfo: [AnyHashable : Any]) {
self.delegate?.fa_rewardedAdDidShow(ad: self)
}
func tpInterstitialAdLoadFailWithError(_ error: any Error) {
self.delegate?.fa_rewardedAd(ad: self, didLoadFail: error)
}
func tpInterstitialAdShow(_ adInfo: [AnyHashable : Any], didFailWithError error: any Error) {
self.delegate?.fa_rewardedAd(ad: self, didDisplayFail: error)
}
func tpInterstitialAdClicked(_ adInfo: [AnyHashable : Any]) {
self.delegate?.fa_rewardedAdDidClick(ad: self)
}
func tpInterstitialAdDismissed(_ adInfo: [AnyHashable : Any]) {
self.delegate?.fa_rewardedAdDidDismiss(ad: self)
}
}
#endif

View File

@ -0,0 +1,90 @@
//
// FATradPlusRewardedAd.swift
// Fableon
//
// Created by on 2026/1/24.
//
import UIKit
#if canImport(TradPlusAds)
import TradPlusAds
#endif
class FATradPlusRewardedAd: NSObject, FARewardedAd {
#if canImport(TradPlusAds)
private var rewardedAd: TradPlusAdRewarded?
#endif
weak var delegate: FARewardedAdDelegate?
var adPlatform: FAAdPlatform = .tradPlus
var adType: FAAdType {
return .rewarded
}
var adUnitID: String {
return self.adPlatform.rewardedUnitId
}
var networkName: String = ""
var isReady: Bool {
#if canImport(TradPlusAds)
return self.rewardedAd?.isAdReady ?? false
#else
return false
#endif
}
func loadAd() {
#if canImport(TradPlusAds)
rewardedAd = TradPlusAdRewarded()
rewardedAd?.setAdUnitID(self.adUnitID)
rewardedAd?.delegate = self
rewardedAd?.loadAd()
#endif
}
func showAd() {
#if canImport(TradPlusAds)
guard isReady else { return }
self.rewardedAd?.showAd(withSceneId: nil)
#endif
}
}
#if canImport(TradPlusAds)
extension FATradPlusRewardedAd: TradPlusADRewardedDelegate {
func tpRewardedAdLoaded(_ adInfo: [AnyHashable : Any]) {
self.networkName = (adInfo["adNetworkName"] as? String) ?? "unknown"
self.delegate?.fa_rewardedAdDidLoadFinish(ad: self)
}
func tpRewardedAdLoadFailWithError(_ error: any Error) {
self.delegate?.fa_rewardedAd(ad: self, didLoadFail: error)
}
func tpRewardedAdImpression(_ adInfo: [AnyHashable : Any]) {
self.delegate?.fa_rewardedAdDidShow(ad: self)
}
func tpRewardedAdShow(_ adInfo: [AnyHashable : Any], didFailWithError error: any Error) {
self.delegate?.fa_rewardedAd(ad: self, didDisplayFail: error)
}
func tpRewardedAdClicked(_ adInfo: [AnyHashable : Any]) {
self.delegate?.fa_rewardedAdDidClick(ad: self)
}
func tpRewardedAdDismissed(_ adInfo: [AnyHashable : Any]) {
self.delegate?.fa_rewardedAdDidDismiss(ad: self)
}
func tpRewardedAdReward(_ adInfo: [AnyHashable : Any]) {
// self.delegate?.fa_rewardedAdDidShow(ad: <#T##any BRRewardedAd#>)
}
}
#endif

View File

@ -12,6 +12,8 @@ import YYText
class FAPayRetainAlert: FABaseAlert {
var buyFinishHandle: (() -> Void)?
var didDismissHandle: (() -> Void)?
var model: FAPayDateModel? {
didSet {
@ -93,6 +95,11 @@ class FAPayRetainAlert: FABaseAlert {
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func dismiss() {
super.dismiss()
self.didDismissHandle?()
}
}

View File

@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x33",
"green" : "0x33",
"red" : "0xCC"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}