Compare commits

...

7 Commits
1.1.3 ... main

Author SHA1 Message Date
zeng
b02f655fb1 请求增加idfv 2025-07-19 15:55:12 +08:00
zeng
ab478455fd 再次优化内购 2025-07-19 15:39:24 +08:00
zeng
c1ad644d81 播放页面优化 2025-07-19 15:27:22 +08:00
zeng
01eb1c6c6a 内购价格随地区变化 2025-07-19 13:57:19 +08:00
zeng
ee36da46d3 Merge branch 'main' of https://git.qinjiu8.com/zengjx/MoviaBox 2025-07-17 10:59:51 +08:00
zeng
d6042c7786 广告优化 2025-07-17 10:59:48 +08:00
zjx
ac09b0612b 广告优化 2025-07-17 10:59:12 +08:00
18 changed files with 343 additions and 107 deletions

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
1B222BCF2E2B80DD002F5A68 /* SPPayTemplateRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B222BCE2E2B80DD002F5A68 /* SPPayTemplateRequest.swift */; };
1BB91D102E04FD6A00A2C715 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB91BBD2E04FD6A00A2C715 /* AppDelegate.swift */; };
1BB91D112E04FD6A00A2C715 /* AppDelegate+APNS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB91BBE2E04FD6A00A2C715 /* AppDelegate+APNS.swift */; };
1BB91D122E04FD6A00A2C715 /* AppDelegate+Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BB91BBF2E04FD6A00A2C715 /* AppDelegate+Config.swift */; };
@ -316,7 +317,7 @@
/* Begin PBXFileReference section */
0538826A0638D33FEF3A2E38 /* Pods-ThimraTV.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ThimraTV.debug.xcconfig"; path = "Target Support Files/Pods-ThimraTV/Pods-ThimraTV.debug.xcconfig"; sourceTree = "<group>"; };
109EB01BE447EE135493CA38 /* Pods-MoviaBox.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MoviaBox.release.xcconfig"; path = "Target Support Files/Pods-MoviaBox/Pods-MoviaBox.release.xcconfig"; sourceTree = "<group>"; };
1B222BCE2E2B80DD002F5A68 /* SPPayTemplateRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPPayTemplateRequest.swift; sourceTree = "<group>"; };
1BB91BBD2E04FD6A00A2C715 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
1BB91BBE2E04FD6A00A2C715 /* AppDelegate+APNS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+APNS.swift"; sourceTree = "<group>"; };
1BB91BBF2E04FD6A00A2C715 /* AppDelegate+Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Config.swift"; sourceTree = "<group>"; };
@ -606,11 +607,8 @@
1BF513202E2662DC009750EA /* SPAdmobBannerAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAdmobBannerAd.swift; sourceTree = "<group>"; };
1BF513222E273479009750EA /* SPApplovinBannerAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPApplovinBannerAd.swift; sourceTree = "<group>"; };
1DBC40592DA4EDFC0093FCB0 /* ThimraTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ThimraTV.app; sourceTree = BUILT_PRODUCTS_DIR; };
1F666DE0B12C863F26BE5027 /* Pods-MoviaBox.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MoviaBox.debug.xcconfig"; path = "Target Support Files/Pods-MoviaBox/Pods-MoviaBox.debug.xcconfig"; sourceTree = "<group>"; };
A1174E10BCF2C606F7818792 /* Pods-ThimraTV.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ThimraTV.release.xcconfig"; path = "Target Support Files/Pods-ThimraTV/Pods-ThimraTV.release.xcconfig"; sourceTree = "<group>"; };
B64805795B479324EB764157 /* Pods_ThimraTV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ThimraTV.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F7763FEFB6BEB1A75D6FBA0A /* Pods-Thimra.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Thimra.debug.xcconfig"; path = "Target Support Files/Pods-Thimra/Pods-Thimra.debug.xcconfig"; sourceTree = "<group>"; };
FEA583158A7C05D8D7C5A9FC /* Pods-Thimra.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Thimra.release.xcconfig"; path = "Target Support Files/Pods-Thimra/Pods-Thimra.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -640,10 +638,6 @@
0061C3496D158807460301A9 /* Pods */ = {
isa = PBXGroup;
children = (
F7763FEFB6BEB1A75D6FBA0A /* Pods-Thimra.debug.xcconfig */,
FEA583158A7C05D8D7C5A9FC /* Pods-Thimra.release.xcconfig */,
1F666DE0B12C863F26BE5027 /* Pods-MoviaBox.debug.xcconfig */,
109EB01BE447EE135493CA38 /* Pods-MoviaBox.release.xcconfig */,
0538826A0638D33FEF3A2E38 /* Pods-ThimraTV.debug.xcconfig */,
A1174E10BCF2C606F7818792 /* Pods-ThimraTV.release.xcconfig */,
);
@ -1275,6 +1269,7 @@
1BB91CC82E04FD6A00A2C715 /* SPIAPOrderModel.swift */,
1BB91CC92E04FD6A00A2C715 /* SPIAPVerifyModel.swift */,
1BB91CCA2E04FD6A00A2C715 /* SPWaitRestoreModel.swift */,
1B222BCE2E2B80DD002F5A68 /* SPPayTemplateRequest.swift */,
);
path = SPIAPManager;
sourceTree = "<group>";
@ -1882,6 +1877,7 @@
1BB91D872E04FD6A00A2C715 /* SPAboutUsViewController.swift in Sources */,
1BB91D882E04FD6A00A2C715 /* SPDeleteAccountViewController.swift in Sources */,
1BB91D892E04FD6A00A2C715 /* SPFeedbackViewController.swift in Sources */,
1B222BCF2E2B80DD002F5A68 /* SPPayTemplateRequest.swift in Sources */,
1BB91D8A2E04FD6A00A2C715 /* SPLanguageViewController.swift in Sources */,
1BB91D8B2E04FD6A00A2C715 /* SPMineViewController.swift in Sources */,
1BB91D8C2E04FD6A00A2C715 /* SPSettingsViewController.swift in Sources */,

View File

@ -23,7 +23,7 @@ class SPVideoAPI: NSObject {
var param = SPNetworkParameters(path: "/getVideoDetails")
param.method = .get
param.parameters = parameters
param.isLoding = true
param.isLoding = false
param.isToast = false
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPVideoDetailModel>) in

View File

@ -14,15 +14,20 @@ class SPWalletAPI: NSObject {
}
///
static func requestPayTemplate(completer: ((_ model: SPPayTemplateModel?) -> Void)?) {
static func requestPayTemplate(isLoding: Bool = false, isToast: Bool = true, completer: ((_ model: SPPayTemplateModel?) -> Void)?) {
var param = SPNetworkParameters(path: "/paySettingsV3")
param.method = .get
param.isToast = isToast
param.isLoding = false
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPPayTemplateModel>) in
completer?(response.data)
}
}
///
static func requestCreateOrder(payId: String, shortPlayId: String, videoId: String, completer: ((_ orderModel: SPIAPOrderModel?) -> Void)?) {
var param = SPNetworkParameters(path: "/createOrder")

View File

@ -90,7 +90,7 @@ extension SPApi: TargetType {
"system-type" : "ios",
"idfa" : SPAPPTool.getIdfa(),
"model" : UIDevice.sp_machineModelName(),
// "security" : "false",
"device-gaid" : JXUUID.idfv()
]
//
dic["authorization"] = userToken

View File

@ -28,6 +28,8 @@ class SPMineViewController: SPViewController {
private var needShowRewardedAd: Bool = false
weak var vipAlertView: SPVipAlertView?
private var payTemplateRequest: SPPayTemplateRequest?
//MARK: UI 
private lazy var headerView: SPMineHeaderView = {
let view = SPMineHeaderView()
@ -101,7 +103,10 @@ extension SPMineViewController {
guard SPLoginManager.manager.userInfo?.is_vip != true else { return }
guard SPVipAlertView.isShowAlert else { return }
SPWalletAPI.requestPayTemplate { model in
self.payTemplateRequest = SPPayTemplateRequest()
self.payTemplateRequest?.requestProducts(isToast: false) { [weak self] model in
guard let self = self else { return }
guard let list = model?.list_sub_vip, list.count > 0 else { return }
if !self.isDidAppear { return }
if self.vipAlertView != nil { return }

View File

@ -23,7 +23,9 @@ class SPPlayerDetailViewController: SPPlayerListViewController {
var activityId: String?
var playHistoryModel: SPShortModel?
private var detailModel: SPVideoDetailModel?
// private var detailModel: SPVideoDetailModel?
private var detailDataArr: [Any] = []
///
private var lastUploadTime: Int = 0
@ -160,6 +162,7 @@ class SPPlayerDetailViewController: SPPlayerListViewController {
self.pause()
let view = SPPlayerDetailRecommandView()
view.currentVideoInfo = self.viewModel.currentPlayer?.videoInfo
view.clickCloseButton = { [weak self] in
guard let self = self else { return }
self._handleBack()
@ -246,12 +249,16 @@ extension SPPlayerDetailViewController {
extension SPPlayerDetailViewController {
private func onEpisode() {
guard detailDataArr.count > 0 else { return }
guard let detailModel = detailDataArr[self.viewModel.currentIndexPath.section] as? SPVideoDetailModel else { return }
let view = SPEpisodeView()
view.dataArr = detailModel?.episodeList ?? []
view.shortModel = detailModel?.shortPlayInfo
view.dataArr = detailModel.episodeList ?? []
view.shortModel = detailModel.shortPlayInfo
view.currentIndex = self.viewModel.currentIndexPath.row
view.didSelectedIndex = { [weak self] (index) in
self?.scrollToItem(indexPath: IndexPath(row: index, section: 0), animated: false)
self?.scrollToItem(indexPath: IndexPath(row: index, section: self?.viewModel.currentIndexPath.section ?? 0), animated: false)
}
view.present(in: nil)
self.episodeView = view
@ -320,7 +327,7 @@ extension SPPlayerDetailViewController {
}
///
@objc private func reachabilityDidChangeNotification() {
if SPNetworkReachabilityManager.manager.isReachable == true && self.detailModel == nil {
if SPNetworkReachabilityManager.manager.isReachable == true && self.detailDataArr.isEmpty {
self.requestDetailData()
}
}
@ -344,6 +351,8 @@ extension SPPlayerDetailViewController {
//MARK: -------------- SPPlayerListViewControllerDataSource --------------
extension SPPlayerDetailViewController: SPPlayerListViewControllerDataSource, SPPlayerListViewControllerDelegate {
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell {
let detailModel = self.detailDataArr[indexPath.section] as? SPVideoDetailModel
if let cell = oldCell as? SPPlayerDetailCell {
cell.shortModel = detailModel?.shortPlayInfo
cell.videoInfo = detailModel?.episodeList?[indexPath.row]
@ -371,15 +380,40 @@ extension SPPlayerDetailViewController: SPPlayerListViewControllerDataSource, SP
}
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int {
return detailModel?.episodeList?.count ?? 0
if let model = self.detailDataArr[section] as? SPVideoDetailModel {
return model.episodeList?.count ?? 0
} else {
return 1
}
}
func sp_playerListViewController(_ viewController: SPPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath) {
self.episodeView?.currentIndex = indexPath.row
let videoInfo = detailModel?.episodeList?[indexPath.row]
// self.episodeView?.currentIndex = indexPath.row
if let detailModel = self.detailDataArr[indexPath.section] as? SPVideoDetailModel {
let videoInfo = detailModel.episodeList?[indexPath.row]
titleLabel.text = detailModel.shortPlayInfo?.name
episodeLabel.text = "\(videoInfo?.episode ?? "0")/\(detailModel.shortPlayInfo?.episode_total ?? 0)"
} else {
titleLabel.text = ""
episodeLabel.text = ""
}
}
func sp_numberOfSections(in viewController: SPPlayerListViewController) -> Int {
return self.detailDataArr.count
}
func sp_shouldAutoScrollNextEpisode(_ viewController: SPPlayerListViewController) -> Bool {
if episodeView != nil {
return false
}
return true
}
func sp_playerViewControllerLoadMoreData(playerViewController: SPPlayerListViewController) {
titleLabel.text = detailModel?.shortPlayInfo?.name
episodeLabel.text = "\(videoInfo?.episode ?? "0")/\(detailModel?.shortPlayInfo?.episode_total ?? 0)"
}
}
@ -402,7 +436,10 @@ extension SPPlayerDetailViewController {
recommandTimer = nil
recommandTimer = Timer.scheduledTimer(timeInterval: 6, target: YYWeakProxy(target: self), selector: #selector(handleRecommandTimer), userInfo: nil, repeats: false)
SPHUD.show(containerView: self.view)
SPVideoAPI.requestVideoDetail(videoId: videoId, shortPlayId: shortPlayId, activityId: activityId) { [weak self] model, code, msg in
SPHUD.dismiss()
guard let self = self else { return }
if code == 10014 {
self.navigationController?.popViewController(animated: true)
@ -410,16 +447,19 @@ extension SPPlayerDetailViewController {
}
guard let model = model else { return }
self.detailModel = model
self.detailDataArr.removeAll()
self.detailDataArr.append(model)
self.reloadData { [weak self] in
guard let self = self else { return }
if let indexPath = indexPath, indexPath.row < (model.episodeList?.count ?? 0) {
self.scrollToItem(indexPath: indexPath, animated: false)
} else if let videoInfo = self.detailModel?.video_info {
} else if let videoInfo = model.video_info {
var row: Int?
self.detailModel?.episodeList?.enumerated().forEach({
model.episodeList?.enumerated().forEach({
if $1.id == videoInfo.id {
row = $0
}

View File

@ -17,15 +17,14 @@ import AVKit
@objc optional func sp_playerViewControllerShouldLoadMoreData(playerViewController: SPPlayerListViewController) -> Bool
///
@objc optional func sp_playerViewControllerLoadMoreData(playerViewController: SPPlayerListViewController)
///
@objc optional func sp_playerViewControllerLoadUpMoreData(playerViewController: SPPlayerListViewController)
///
@objc optional func sp_playerListViewController(_ viewController: SPPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath)
// @objc optional func sp_playerListViewController(_ viewController: SPPlayerListViewController, didScrollFromIndex fromIndex: Int, toIndex: Int)
///
// @objc optional func yd_playerViewController(playerListViewController: BCListPlayerViewController, didShowPlayerPage playerViewController: YDBasePlayerViewController)
///
@objc optional func sp_shouldAutoScrollNextEpisode(_ viewController: SPPlayerListViewController) -> Bool
}
@objc protocol SPPlayerListViewControllerDataSource {
@ -35,7 +34,7 @@ import AVKit
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int
@objc optional func sp_numberOfSections(in viewController: SPPlayerListViewController) -> Int
}
@ -59,9 +58,6 @@ class SPPlayerListViewController: SPViewController {
///
var autoNextEpisode = false
///
private(set) var isFirstPlay = true
private(set) var viewModel = SPPlayerListViewModel()
@ -173,20 +169,10 @@ class SPPlayerListViewController: SPViewController {
self.viewModel.isPlaying = true
if getDataCount() - self.viewModel.currentIndexPath.row <= 2 {
if (self.collectionView.contentSize.height - self.collectionView.contentOffset.y) / self.contentSize.height <= 3 {
self.loadMoreData()
}
if isFirstPlay {
isFirstPlay = false
let offset = self.collectionView.contentOffset.y + 0.2
self.collectionView.setContentOffset(CGPoint(x: 0, y: offset), animated: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
let offset = self.collectionView.contentOffset.y
self.collectionView.setContentOffset(CGPoint(x: 0, y: floor(offset)), animated: false)
}
}
}
func pause() {
@ -210,7 +196,8 @@ class SPPlayerListViewController: SPViewController {
}
func getDataCount() -> Int {
return self.collectionView(self.collectionView, numberOfItemsInSection: 0)
return Int(self.collectionView.contentSize.height / self.contentSize.height)
// return self.collectionView(self.collectionView, numberOfItemsInSection: 0)
}
func scrollToItem(indexPath: IndexPath, animated: Bool = true, completer: (() -> Void)? = nil) {
@ -232,10 +219,19 @@ class SPPlayerListViewController: SPViewController {
///
func currentPlayFinish() {
if self.autoNextEpisode {
scrollToNextEpisode()
}
self.viewModel.currentPlayer?.videoInfo?.play_seconds = 0
var autoNextEpisode = self.autoNextEpisode
if let result = self.delegate?.sp_shouldAutoScrollNextEpisode?(self) {
autoNextEpisode = result
}
if autoNextEpisode {
scrollToNextEpisode()
} else {
viewModel.currentPlayer?.replay()
}
}
///
@ -356,6 +352,10 @@ extension SPPlayerListViewController: UICollectionViewDelegate, UICollectionView
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return self.dataSource?.sp_numberOfSections?(in: self) ?? 1
}
//
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollDidEnd(scrollView)
@ -415,13 +415,6 @@ extension SPPlayerListViewController {
}
}
private func loadUpMoreData() {
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
// guard let self = self else { return }
// }
self.delegate?.sp_playerViewControllerLoadUpMoreData?(playerViewController: self)
}
private func didChangeIndexPathForVisible() {
self.delegate?.sp_playerListViewController?(self, didChangeIndexPathForVisible: self.viewModel.currentIndexPath)
}

View File

@ -24,6 +24,8 @@ class SPPlayBuyView: HWPanModalContentView {
}
}
private var payTemplateRequest: SPPayTemplateRequest?
//MARK: UI
private lazy var bgView: UIImageView = {
let view = UIImageView(image: UIImage(named: "buy_bg_image_01"))
@ -246,27 +248,28 @@ extension SPPlayBuyView {
///
private func requestPayTemplate() {
SPWalletAPI.requestPayTemplate { [weak self] templateModel in
self.payTemplateRequest = SPPayTemplateRequest()
self.payTemplateRequest?.requestProducts { [weak self] model in
guard let self = self else { return }
self.stackView.removeAllArrangedSubview()
if let sort = templateModel?.sort, sort.count > 0 {
if let sort = model?.sort, sort.count > 0 {
sort.forEach {
if $0 == .vip {
self.addMemberView(list: templateModel?.list_sub_vip)
self.addMemberView(list: model?.list_sub_vip)
} else if $0 == .coin {
self.addCoinView(list: templateModel?.list_coins)
self.addCoinView(list: model?.list_coins)
}
}
} else {
self.addMemberView(list: templateModel?.list_sub_vip)
self.addCoinView(list: templateModel?.list_coins)
self.addMemberView(list: model?.list_sub_vip)
self.addCoinView(list: model?.list_coins)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.panModalSetNeedsLayoutUpdate()
}
}
}

View File

@ -14,6 +14,14 @@ class SPPlayerDetailRecommandView: HWPanModalContentView {
var clickCloseButton: (() -> Void)?
var clickPlayButton: ((_ model: SPShortModel) -> Void)?
var currentVideoInfo: SPVideoInfoModel? {
didSet {
if SPLoginManager.manager.userInfo?.user_level == .ad {
bannerAd.videoInfo = currentVideoInfo
}
}
}
private var _currentCell: SPPlayerDetailRecommandCell?
private var currentCell: SPPlayerDetailRecommandCell? {

View File

@ -9,6 +9,8 @@ import UIKit
class SPStoreViewController: SPViewController {
private var payTemplateRequest: SPPayTemplateRequest?
//MARK: UI
private lazy var scrollView: SPScrollView = {
let scrollView = SPScrollView()
@ -184,21 +186,23 @@ extension SPStoreViewController {
///
private func requestPayTemplate() {
SPWalletAPI.requestPayTemplate { [weak self] templateModel in
self.payTemplateRequest = SPPayTemplateRequest()
self.payTemplateRequest?.requestProducts { [weak self] model in
guard let self = self else { return }
self.stackView.removeAllArrangedSubview()
if let sort = templateModel?.sort, sort.count > 0 {
if let sort = model?.sort, sort.count > 0 {
sort.forEach {
if $0 == .vip {
self.addMemberView(list: templateModel?.list_sub_vip)
self.addMemberView(list: model?.list_sub_vip)
} else if $0 == .coin {
self.addCoinView(list: templateModel?.list_coins)
self.addCoinView(list: model?.list_coins)
}
}
} else {
self.addMemberView(list: templateModel?.list_sub_vip)
self.addCoinView(list: templateModel?.list_coins)
self.addMemberView(list: model?.list_sub_vip)
self.addCoinView(list: model?.list_coins)
}
}
}

View File

@ -14,6 +14,9 @@ class SPApplovinBannerAd: NSObject, SPBannerAd {
var delegate: (any SPBannerAdDelegate)?
///
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)
@ -53,12 +56,18 @@ extension SPApplovinBannerAd: MAAdViewAdDelegate {
}
func didLoad(_ ad: MAAd) {
self.delegate?.bannerAdDidLoadFinish?(bannerAd: self)
if !isLoaded {
isLoaded = true
self.delegate?.bannerAdDidLoadFinish?(bannerAd: self)
}
}
func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
let nsError = NSError(domain: error.message, code: error.code.rawValue)
self.delegate?.bannerAd?(bannerAd: self, didLoadFail: nsError)
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) {

View File

@ -57,6 +57,8 @@ class SPBannerAdManager: NSObject {
weak var delegate: SPBannerAdManagerDelegate?
var videoInfo: SPVideoInfoModel?
private lazy var bannerAd: SPBannerAd = {
let ad = SPAdmobBannerAd()
ad.delegate = self
@ -122,6 +124,8 @@ extension SPBannerAdManager {
model.ad_platform_key = SPAdPlatformKey(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
SPStatAPI.requestStatAd(model: model)
}

View File

@ -12,9 +12,9 @@ class SPHUD: NSObject {
SVProgressHUD.setDefaultMaskType(.clear)
}
static func show(status: String? = nil) {
static func show(status: String? = nil, containerView: UIView? = nil) {
SVProgressHUD.setContainerView(containerView)
SVProgressHUD.setDefaultMaskType(.clear)
// SVProgressHUD.show()
SVProgressHUD.show(withStatus: status)
}

View File

@ -6,6 +6,7 @@
//
import UIKit
import StoreKit
class SPIAPManager: NSObject {
typealias CompletionHandler = ((_ finish: Bool) -> Void)
@ -18,6 +19,8 @@ class SPIAPManager: NSObject {
///
private var completionHandler: CompletionHandler?
private var productListHandler: ((_ products: [SKProduct]) -> Void)?
private lazy var iapManager: JXIAPManager = {
let manager = JXIAPManager()
manager.delegate = self
@ -119,6 +122,17 @@ class SPIAPManager: NSObject {
}
}
func requestProductList(productIdArr: [String], completer: ((_ products: [SKProduct]) -> Void)?) {
self.productListHandler = completer
self.iapManager.requestProductList(productIdArr: productIdArr)
}
func getProductId(templateId: String?) -> String? {
guard let templateId = templateId else { return nil }
return SPIAPManager.IAPPrefix + templateId
}
}
//MARK: -------------- JXIAPManagerDelegate --------------
@ -182,6 +196,13 @@ extension SPIAPManager: JXIAPManagerDelegate {
self.completionHandler?(false)
}
func jx_iapPayGotProducts(products: [SKProduct]) {
self.productListHandler?(products)
self.productListHandler = nil
}
}
extension SPIAPManager {

View File

@ -0,0 +1,101 @@
//
// SPPayTemplateRequest.swift
// ThimraTV
//
// Created by on 2025/7/19.
//
import UIKit
import StoreKit
class SPPayTemplateRequest: NSObject {
private var oldTemplateModel: SPPayTemplateModel?
private var completerBlock: ((_ model: SPPayTemplateModel?) -> Void)?
private var isLoding = false
private var isToast = false
func requestProducts(isLoding: Bool = false, isToast: Bool = true, completer: ((_ model: SPPayTemplateModel?) -> Void)?) {
self.completerBlock = completer
self.isLoding = isLoding
self.isToast = isToast
if isLoding {
SPHUD.show()
}
SPWalletAPI.requestPayTemplate(isToast: isToast) { [weak self] model in
guard let self = self else { return }
guard let model = model else {
if isLoding {
SPHUD.dismiss()
}
self.completerBlock?(nil)
return
}
self.oldTemplateModel = model
var productIdArr: [String] = []
model.list_sub_vip?.forEach { item in
productIdArr.append(SPIAPManager.manager.getProductId(templateId: item.ios_template_id) ?? "")
}
model.list_coins?.forEach { item in
productIdArr.append(SPIAPManager.manager.getProductId(templateId: item.ios_template_id) ?? "")
}
let set = Set(productIdArr)
let productsRequest = SKProductsRequest(productIdentifiers: set)
productsRequest.delegate = self
productsRequest.start()
}
}
}
//MARK: -------------- SKProductsRequestDelegate --------------
extension SPPayTemplateRequest: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if isLoding {
SPHUD.dismiss()
}
guard let templateModel = self.oldTemplateModel else { return }
let products = response.products
var newCoinList: [SPPayTemplateItem] = []
var newVipList: [SPPayTemplateItem] = []
templateModel.list_coins?.forEach { item in
let productId = SPIAPManager.manager.getProductId(templateId: item.ios_template_id) ?? ""
for product in products {
if productId == product.productIdentifier {
item.price = product.price.stringValue
item.currency = product.priceLocale.currencySymbol
newCoinList.append(item)
break
}
}
}
templateModel.list_sub_vip?.forEach { item in
let productId = SPIAPManager.manager.getProductId(templateId: item.ios_template_id) ?? ""
for product in products {
if productId == product.productIdentifier {
item.price = product.price.stringValue
item.currency = product.priceLocale.currencySymbol
newVipList.append(item)
break
}
}
}
templateModel.list_coins = newCoinList
templateModel.list_sub_vip = newVipList
DispatchQueue.main.async {
self.completerBlock?(templateModel)
}
}
}

View File

@ -10,7 +10,7 @@ import StoreKit
@objc protocol JXIAPManagerDelegate {
///
@objc optional func jx_iapPayGotProducts(productIds: [String])
@objc optional func jx_iapPayGotProducts(products: [SKProduct])
///
@objc optional func jx_iapPaySuccess(productId: String, receipt: String, transactionIdentifier: String?)
///
@ -34,19 +34,29 @@ import StoreKit
case cancelled
///
case noProduct
}
class JXIAPManager: NSObject {
enum OperationType {
case idle
case buy
case request
}
static let manager: JXIAPManager = JXIAPManager()
weak var delegate: JXIAPManagerDelegate?
///
private var operationType = OperationType.idle
private var payment: SKPayment?
private var product: SKProduct?
private var productId: String?
private var discount: SKPaymentDiscount?
private var orderId: String?
private var applicationUsername: String? {
get {
@ -80,10 +90,47 @@ class JXIAPManager: NSObject {
SKPaymentQueue.default().add(self)
}
func start(productId: String, orderId: String) {
func showCodeRedemption() {
SKPaymentQueue.default().presentCodeRedemptionSheet()
}
func requestProductList(productIdArr: [String]) {
// guard self.operationType == .idle else { return }
self.operationType = .request
let set = Set(productIdArr)
let productsRequest = SKProductsRequest(productIdentifiers: set)
productsRequest.delegate = self
productsRequest.start()
}
// ///
// func buyProduct(product: SKProduct, orderId: String, discount: SKPaymentDiscount? = nil) {
// guard self.operationType == .idle else { return }
// self.operationType = .buy
//
// self.product = product
// self.orderId = orderId
//
// //
// let payment = SKMutablePayment(product: product)
// payment.applicationUsername = applicationUsername
// if let discount = discount {
// payment.paymentDiscount = discount
// }
//
// //
// SKPaymentQueue.default().add(payment)
// }
func start(productId: String, orderId: String, discount: SKPaymentDiscount? = nil) {
guard self.operationType == .idle else { return }
self.operationType = .buy
self.product = nil
self.productId = productId
self.orderId = orderId
self.discount = discount
let set = Set([productId])
let productsRequest = SKProductsRequest(productIdentifiers: set)
@ -98,9 +145,13 @@ class JXIAPManager: NSObject {
//
let payment = SKMutablePayment(product: product)
payment.applicationUsername = applicationUsername
spLog(message: payment.applicationUsername)
if let discount = self.discount {
payment.paymentDiscount = discount
self.discount = nil
}
self.payment = payment
//
SKPaymentQueue.default().add(payment)
}
@ -110,18 +161,27 @@ class JXIAPManager: NSObject {
//MARK: -------------- SKProductsRequestDelegate --------------
extension JXIAPManager: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
guard let product = response.products.first else {
if self.operationType == .request {
DispatchQueue.main.async {
if let productId = self.productId {
self.productId = nil
self.delegate?.jx_iapPayFailed?(productId: productId, code: .noProduct)
}
self.delegate?.jx_iapPayGotProducts?(products: response.products)
}
return
}
self.product = product
self.operationType = .idle
} else if self.operationType == .buy {
guard let product = response.products.first else {
DispatchQueue.main.async {
if let productId = self.productId {
self.productId = nil
self.delegate?.jx_iapPayFailed?(productId: productId, code: .noProduct)
}
}
return
}
self.product = product
self.buyProduct()
}
self.buyProduct()
}
}
@ -131,7 +191,6 @@ extension JXIAPManager: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
spLog(message: "transactionState = \(transaction.transactionState)")
switch transaction.transactionState {
case .purchased:
DispatchQueue.main.async {
@ -171,33 +230,21 @@ extension JXIAPManager: SKPaymentTransactionObserver {
extension JXIAPManager {
private func completeTransaction(transaction: SKPaymentTransaction) {
//
// if let _ = transaction.original, transaction.payment.applicationUsername == nil {
// return
// }
//
// if let _ = transaction.original, transaction.payment.applicationUsername != nil {
// self.delegate?.jx_iapPayFailed?(productId: productId, code: .unknown)
// return
// }
spLog(message: "transactionDate = \(String(describing: transaction.transactionDate))")
spLog(message: "nowDate = \(Date())")
spLog(message: "productIdentifier = \(transaction.payment.productIdentifier)")
guard let productId = self.productId, productId == transaction.payment.productIdentifier else { return }
self.productId = nil
guard let receiptURL = Bundle.main.appStoreReceiptURL else { return }
let receiptData = NSData(contentsOf: receiptURL)
guard let encodeStr = receiptData?.base64EncodedString(options: .endLineWithLineFeed) else { return }
guard let transactionIdentifier = transaction.transactionIdentifier else { return }
guard let productId = self.productId, productId == transaction.payment.productIdentifier else { return }
self.operationType = .idle
self.productId = nil
self.delegate?.jx_iapPaySuccess?(productId: productId, receipt: encodeStr, transactionIdentifier: transactionIdentifier)
}
private func failedTransaction(transaction: SKPaymentTransaction) {
self.operationType = .idle
let error = transaction.error as? SKError
guard let productId = self.productId else { return }
self.productId = nil

View File

@ -15,6 +15,6 @@
/**
app后
*/
+ (nonnull NSString *)systemUUID;
+ (nonnull NSString *)idfv;
@end

View File

@ -39,7 +39,7 @@ static NSString *const uuidKey = @"com.JXUUID";
return idfa;
}
+ (nonnull NSString *)systemUUID
+ (nonnull NSString *)idfv
{
return [UIDevice currentDevice].identifierForVendor.UUIDString;
}