Compare commits

..

19 Commits
1.1.2 ... 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
zeng
de51890ff7 1.1.3提审 2025-07-16 17:06:13 +08:00
zeng
a6219af9ed 广告bug修复 2025-07-15 15:42:15 +08:00
zjx
73512c7ef1 网络状态监听优化 2025-07-14 17:25:35 +08:00
zeng
a399554e58 添加applovin广告,广告流程优化 2025-07-11 17:43:00 +08:00
zeng
ffc984babe 横幅广告 2025-07-11 14:49:02 +08:00
zeng
c3104280ea 详情推荐激励视频 2025-07-11 13:33:18 +08:00
zeng
7736f5ed02 启屏广告 2025-07-11 11:27:45 +08:00
zeng
0232c818d8 看广告签到 2025-07-11 09:41:41 +08:00
zeng
1771235bec 我的页面激励广告优化 2025-07-10 16:59:26 +08:00
zeng
0375fc09a9 no message 2025-07-10 16:35:54 +08:00
zeng
9011b29cd9 广告解锁视频开发 2025-07-10 16:35:46 +08:00
zeng
103a5ea064 修复详情页面视频被删除的闪退问题 2025-07-09 09:58:08 +08:00
64 changed files with 3270 additions and 250 deletions

View File

@ -39,3 +39,6 @@ NO.118 Xinling Rd Shanghai Branch
沙盒账号:
jiaer@test.com
Cje12345
thimra@test.com
Discover2024

13
Podfile
View File

@ -35,4 +35,17 @@ target 'ThimraTV' do
pod 'Adjust' # Adjust
pod 'GoogleMobileAdsMediationAppLovin'
pod 'GoogleMobileAdsMediationIronSource'
pod 'GoogleMobileAdsMediationFacebook'
pod 'GoogleMobileAdsMediationMintegral'
pod 'GoogleMobileAdsMediationPangle'
pod 'AppLovinMediationFacebookAdapter'
pod 'AppLovinMediationByteDanceAdapter'
pod 'AppLovinMediationIronSourceAdapter'
pod 'AppLovinMediationMintegralAdapter'
pod 'AppLovinMediationGoogleAdapter'
end

View File

@ -4,13 +4,110 @@ PODS:
- Adjust/Adjust (5.4.0):
- AdjustSignature (= 3.35.2)
- AdjustSignature (3.35.2)
- Ads-Global (7.1.1.1):
- Ads-Global/BUAdSDK (= 7.1.1.1)
- Ads-Global/BUAdSDK (7.1.1.1)
- Alamofire (5.10.2)
- AppLovinMediationByteDanceAdapter (7.1.1.1.0):
- Ads-Global (= 7.1.1.1)
- AppLovinSDK (>= 13.0.0)
- AppLovinMediationFacebookAdapter (6.17.1.0):
- AppLovinSDK (>= 13.0.0)
- FBAudienceNetwork (= 6.17.1)
- AppLovinMediationGoogleAdapter (12.5.0.0):
- AppLovinSDK (>= 13.0.0)
- Google-Mobile-Ads-SDK (= 12.5.0)
- AppLovinMediationIronSourceAdapter (8.9.0.0.0):
- AppLovinSDK (>= 13.0.0)
- IronSourceSDK (= 8.9.0.0)
- AppLovinMediationMintegralAdapter (7.7.8.0.0):
- AppLovinSDK (>= 13.0.0)
- MintegralAdSDK (= 7.7.8)
- MintegralAdSDK/BidSplashAd (= 7.7.8)
- AppLovinSDK (13.3.0)
- CocoaAsyncSocket (7.6.5)
- EmptyDataSet-Swift (5.0.0)
- FBAudienceNetwork (6.17.1)
- Google-Mobile-Ads-SDK (12.5.0):
- GoogleUserMessagingPlatform (>= 1.1)
- GoogleMobileAdsMediationAppLovin (13.3.0.0):
- AppLovinSDK (= 13.3.0)
- Google-Mobile-Ads-SDK (~> 12.0)
- GoogleMobileAdsMediationFacebook (6.17.1.0):
- FBAudienceNetwork (= 6.17.1)
- Google-Mobile-Ads-SDK (~> 12.0)
- GoogleMobileAdsMediationIronSource (8.9.0.0.0):
- Google-Mobile-Ads-SDK (~> 12.0)
- IronSourceSDK (= 8.9.0.0)
- GoogleMobileAdsMediationMintegral (7.7.8.0):
- Google-Mobile-Ads-SDK (~> 12.0)
- MintegralAdSDK/All (= 7.7.8)
- GoogleMobileAdsMediationPangle (7.1.1.1.0):
- Ads-Global (= 7.1.1.1)
- Google-Mobile-Ads-SDK (~> 12.0)
- GoogleUserMessagingPlatform (3.0.0)
- HWPanModal (0.9.9)
- IronSourceAdQualitySDK (7.24.3)
- IronSourceSDK (8.9.0.0):
- IronSourceSDK/AdQuality (= 8.9.0.0)
- IronSourceSDK/Ads (= 8.9.0.0)
- IronSourceSDK/AdQuality (8.9.0.0):
- IronSourceAdQualitySDK (~> 7.24.3)
- IronSourceSDK/Ads (8.9.0.0)
- Kingfisher (8.3.2)
- KTVHTTPCache (3.0.2):
- CocoaAsyncSocket
- MintegralAdSDK (7.7.8):
- MintegralAdSDK/BannerAd (= 7.7.8)
- MintegralAdSDK/BidBannerAd (= 7.7.8)
- MintegralAdSDK/BidInterstitialVideoAd (= 7.7.8)
- MintegralAdSDK/BidNativeAd (= 7.7.8)
- MintegralAdSDK/BidNewInterstitialAd (= 7.7.8)
- MintegralAdSDK/BidRewardVideoAd (= 7.7.8)
- MintegralAdSDK/InterstitialVideoAd (= 7.7.8)
- MintegralAdSDK/NativeAd (= 7.7.8)
- MintegralAdSDK/NewInterstitialAd (= 7.7.8)
- MintegralAdSDK/RewardVideoAd (= 7.7.8)
- MintegralAdSDK/All (7.7.8):
- MintegralAdSDK/BannerAd
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/InterstitialVideoAd
- MintegralAdSDK/NativeAd
- MintegralAdSDK/NativeAdvancedAd
- MintegralAdSDK/NewInterstitialAd
- MintegralAdSDK/RewardVideoAd
- MintegralAdSDK/SplashAd
- MintegralAdSDK/BannerAd (7.7.8):
- MintegralAdSDK/NativeAd
- MintegralAdSDK/BidBannerAd (7.7.8):
- MintegralAdSDK/BannerAd
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/BidInterstitialVideoAd (7.7.8):
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/InterstitialVideoAd
- MintegralAdSDK/BidNativeAd (7.7.8):
- MintegralAdSDK/NativeAd
- MintegralAdSDK/BidNewInterstitialAd (7.7.8):
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/NewInterstitialAd
- MintegralAdSDK/BidRewardVideoAd (7.7.8):
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/RewardVideoAd
- MintegralAdSDK/BidSplashAd (7.7.8):
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/SplashAd
- MintegralAdSDK/InterstitialVideoAd (7.7.8):
- MintegralAdSDK/NativeAd
- MintegralAdSDK/NativeAd (7.7.8)
- MintegralAdSDK/NativeAdvancedAd (7.7.8):
- MintegralAdSDK/NativeAd
- MintegralAdSDK/NewInterstitialAd (7.7.8):
- MintegralAdSDK/InterstitialVideoAd
- MintegralAdSDK/NativeAd
- MintegralAdSDK/RewardVideoAd (7.7.8):
- MintegralAdSDK/NativeAd
- MintegralAdSDK/SplashAd (7.7.8):
- MintegralAdSDK/NativeAd
- MJRefresh (3.7.9)
- Moya (15.0.0):
- Moya/Core (= 15.0.0)
@ -40,7 +137,17 @@ PODS:
DEPENDENCIES:
- Adjust
- AppLovinMediationByteDanceAdapter
- AppLovinMediationFacebookAdapter
- AppLovinMediationGoogleAdapter
- AppLovinMediationIronSourceAdapter
- AppLovinMediationMintegralAdapter
- EmptyDataSet-Swift
- GoogleMobileAdsMediationAppLovin
- GoogleMobileAdsMediationFacebook
- GoogleMobileAdsMediationIronSource
- GoogleMobileAdsMediationMintegral
- GoogleMobileAdsMediationPangle
- HWPanModal
- Kingfisher
- KTVHTTPCache
@ -60,12 +167,30 @@ SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
- Adjust
- AdjustSignature
- Ads-Global
- Alamofire
- AppLovinMediationByteDanceAdapter
- AppLovinMediationFacebookAdapter
- AppLovinMediationGoogleAdapter
- AppLovinMediationIronSourceAdapter
- AppLovinMediationMintegralAdapter
- AppLovinSDK
- CocoaAsyncSocket
- EmptyDataSet-Swift
- FBAudienceNetwork
- Google-Mobile-Ads-SDK
- GoogleMobileAdsMediationAppLovin
- GoogleMobileAdsMediationFacebook
- GoogleMobileAdsMediationIronSource
- GoogleMobileAdsMediationMintegral
- GoogleMobileAdsMediationPangle
- GoogleUserMessagingPlatform
- HWPanModal
- IronSourceAdQualitySDK
- IronSourceSDK
- Kingfisher
- KTVHTTPCache
- MintegralAdSDK
- MJRefresh
- Moya
- ReachabilitySwift
@ -81,12 +206,30 @@ SPEC REPOS:
SPEC CHECKSUMS:
Adjust: a5f881d0cbfe9a6df979b076dc7116fe19ece797
AdjustSignature: 23b9e5d4adcadffc303bb6b410fde617dd88504f
Ads-Global: b81917c405e94ad38dd45022f0fe17042429f578
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
AppLovinMediationByteDanceAdapter: 82f5a49494a40a3dfa4b959d6f089986c924511b
AppLovinMediationFacebookAdapter: 413c7188e2a21e4c9256aa2b096ddc27240b1443
AppLovinMediationGoogleAdapter: 36d9926e84420b49a7cba45800c6eada3be58b05
AppLovinMediationIronSourceAdapter: 240be5366f735418d01f52304a0dc9c3d521e546
AppLovinMediationMintegralAdapter: 5f64aa8427ba19efea2e0d583abc22dc4633705b
AppLovinSDK: 5ed9dbada0de5a80e4546116994470011ed01f53
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
EmptyDataSet-Swift: eb382c0c87a2d9c678077385a595cec52da38171
FBAudienceNetwork: eb3ffbf2b35df25e21e163657044ffef66616a40
Google-Mobile-Ads-SDK: 6df9aadcee32bce0ff05bcad56ef2c88a4a5e82b
GoogleMobileAdsMediationAppLovin: efd0c6ebe304747b61e115ddc5bd0ed28346f499
GoogleMobileAdsMediationFacebook: 750a2c14ce0bdf813e4e1d4ab4aa283f72f8a875
GoogleMobileAdsMediationIronSource: e0e19ab5f8af55aacb30612ae1c84093db1e2e6e
GoogleMobileAdsMediationMintegral: 0aefaa2f0608c210eb26dd6a6207d8c1bd992bbd
GoogleMobileAdsMediationPangle: 62f9bdabf7ed168f6b0f04831c1cc04da0eec10e
GoogleUserMessagingPlatform: f8d0cdad3ca835406755d0a69aa634f00e76d576
HWPanModal: b57a6717d3cdcd666bff44f9dd2a5be9f4d6f5d2
IronSourceAdQualitySDK: 9974aea1ec73b24fd0eb7e74e7936bcae9fead1d
IronSourceSDK: 285da6cf55cb80a1b258260ee27df8332552ec0d
Kingfisher: 0621d0ac0c78fecb19f6dc5303bde2b52abaf2f5
KTVHTTPCache: 5711692cdf9a5ecfe829b1e16577deb3ffe3dc86
MintegralAdSDK: 07e018290689b906499d8285654bba42681cafcf
MJRefresh: ff9e531227924c84ce459338414550a05d2aea78
Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee
ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda
@ -99,6 +242,6 @@ SPEC CHECKSUMS:
YYKit: 7cda43304a8dc3696c449041e2cb3107b4e236e7
ZFPlayer: 5cf39e8d9f0c2394a014b0db4767b5b5a6bffe13
PODFILE CHECKSUM: 25e7f44d27dd18aad94fde84cae1f0c157c60341
PODFILE CHECKSUM: 12eed82b2517a28e085576b5b940ed72b5ab1b94
COCOAPODS: 1.16.2

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 */; };
@ -267,11 +268,26 @@
1BC1F0D72E0A35EF00B579A4 /* SPVideoRevolutionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BC1F0D62E0A35EF00B579A4 /* SPVideoRevolutionManager.swift */; };
1BC1F0E32E0D268400B579A4 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1BC1F0DC2E0D268400B579A4 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
1BC1F0EB2E0D268B00B579A4 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BC1F0E92E0D268B00B579A4 /* NotificationService.swift */; };
1BDE20132E1E15A700C2C2B5 /* SPAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BDE20122E1E159B00C2C2B5 /* SPAdManager.swift */; };
1BDE20172E1E164600C2C2B5 /* SPAdAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BDE20162E1E163E00C2C2B5 /* SPAdAPI.swift */; };
1BDE20192E1E175800C2C2B5 /* SPAdInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BDE20182E1E175800C2C2B5 /* SPAdInfo.swift */; };
1BDE201E2E1E3D3E00C2C2B5 /* SPRewardedAdManager+Admob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BDE201D2E1E384100C2C2B5 /* SPRewardedAdManager+Admob.swift */; };
1BDE20202E1E669600C2C2B5 /* AttributedString+SPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BDE201F2E1E669000C2C2B5 /* AttributedString+SPAdd.swift */; };
1BE7892B2DCB0E530001A8F1 /* FacebookCore in Frameworks */ = {isa = PBXBuildFile; productRef = 1BE7892A2DCB0E530001A8F1 /* FacebookCore */; };
1BE7892D2DCB0E530001A8F1 /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 1BE7892C2DCB0E530001A8F1 /* FacebookLogin */; };
1BF22FD12DC2169B0082429A /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 1BF22FD02DC2169B0082429A /* FirebaseAnalytics */; };
1BF22FD32DC2169B0082429A /* FirebaseCore in Frameworks */ = {isa = PBXBuildFile; productRef = 1BF22FD22DC2169B0082429A /* FirebaseCore */; };
1BF22FD52DC2169B0082429A /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 1BF22FD42DC2169B0082429A /* FirebaseMessaging */; };
1BF5130C2E1F4660009750EA /* SPRewardedAdManager+AppLovin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF5130B2E1F4654009750EA /* SPRewardedAdManager+AppLovin.swift */; };
1BF5130E2E1F5D9B009750EA /* SPRewardedAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF5130D2E1F5D8F009750EA /* SPRewardedAdManager.swift */; };
1BF513112E1FA138009750EA /* SPStatAdModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF513102E1FA138009750EA /* SPStatAdModel.swift */; };
1BF513142E1FB8C1009750EA /* SPAppOpenAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF513132E1FB8C1009750EA /* SPAppOpenAdManager.swift */; };
1BF513162E20ADB4009750EA /* SPAppOpenAdViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF513152E20ADB4009750EA /* SPAppOpenAdViewController.swift */; };
1BF513192E20DC85009750EA /* SPBannerAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF513182E20DC85009750EA /* SPBannerAdManager.swift */; };
1BF5131B2E265880009750EA /* SPAdmobAppOpenAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF5131A2E265880009750EA /* SPAdmobAppOpenAd.swift */; };
1BF5131D2E265A80009750EA /* SPApplovinAppOpenAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF5131C2E265A80009750EA /* SPApplovinAppOpenAd.swift */; };
1BF513212E2662DC009750EA /* SPAdmobBannerAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF513202E2662DC009750EA /* SPAdmobBannerAd.swift */; };
1BF513232E273482009750EA /* SPApplovinBannerAd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BF513222E273479009750EA /* SPApplovinBannerAd.swift */; };
C3D1CE788CA03A1878493356 /* Pods_ThimraTV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B64805795B479324EB764157 /* Pods_ThimraTV.framework */; };
/* End PBXBuildFile section */
@ -301,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>"; };
@ -575,12 +591,24 @@
1BC1F0DC2E0D268400B579A4 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
1BC1F0E82E0D268B00B579A4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
1BC1F0E92E0D268B00B579A4 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
1BDE20122E1E159B00C2C2B5 /* SPAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAdManager.swift; sourceTree = "<group>"; };
1BDE20162E1E163E00C2C2B5 /* SPAdAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAdAPI.swift; sourceTree = "<group>"; };
1BDE20182E1E175800C2C2B5 /* SPAdInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAdInfo.swift; sourceTree = "<group>"; };
1BDE201D2E1E384100C2C2B5 /* SPRewardedAdManager+Admob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SPRewardedAdManager+Admob.swift"; sourceTree = "<group>"; };
1BDE201F2E1E669000C2C2B5 /* AttributedString+SPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+SPAdd.swift"; sourceTree = "<group>"; };
1BF5130B2E1F4654009750EA /* SPRewardedAdManager+AppLovin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SPRewardedAdManager+AppLovin.swift"; sourceTree = "<group>"; };
1BF5130D2E1F5D8F009750EA /* SPRewardedAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPRewardedAdManager.swift; sourceTree = "<group>"; };
1BF513102E1FA138009750EA /* SPStatAdModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPStatAdModel.swift; sourceTree = "<group>"; };
1BF513132E1FB8C1009750EA /* SPAppOpenAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAppOpenAdManager.swift; sourceTree = "<group>"; };
1BF513152E20ADB4009750EA /* SPAppOpenAdViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAppOpenAdViewController.swift; sourceTree = "<group>"; };
1BF513182E20DC85009750EA /* SPBannerAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPBannerAdManager.swift; sourceTree = "<group>"; };
1BF5131A2E265880009750EA /* SPAdmobAppOpenAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAdmobAppOpenAd.swift; sourceTree = "<group>"; };
1BF5131C2E265A80009750EA /* SPApplovinAppOpenAd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPApplovinAppOpenAd.swift; sourceTree = "<group>"; };
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 */
@ -610,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 */,
);
@ -656,6 +680,7 @@
1BB91BDF2E04FD6A00A2C715 /* Extension */ = {
isa = PBXGroup;
children = (
1BDE201F2E1E669000C2C2B5 /* AttributedString+SPAdd.swift */,
1BB91BCC2E04FD6A00A2C715 /* CGMutablePath+SPAdd.swift */,
1BB91BCD2E04FD6A00A2C715 /* Date+SPAdd.swift */,
1BB91BCE2E04FD6A00A2C715 /* Dictionary+SPAdd.swift */,
@ -692,6 +717,7 @@
1BB91BEC2E04FD6A00A2C715 /* API */ = {
isa = PBXGroup;
children = (
1BDE20162E1E163E00C2C2B5 /* SPAdAPI.swift */,
1BB91BE42E04FD6A00A2C715 /* SPApnsAPI.swift */,
1BB91BE52E04FD6A00A2C715 /* SPHomeAPI.swift */,
1BB91BE62E04FD6A00A2C715 /* SPRewardsAPI.swift */,
@ -813,6 +839,7 @@
isa = PBXGroup;
children = (
1BB91C142E04FD6A00A2C715 /* SPGuideViewController.swift */,
1BF513152E20ADB4009750EA /* SPAppOpenAdViewController.swift */,
);
path = Controller;
sourceTree = "<group>";
@ -1242,6 +1269,7 @@
1BB91CC82E04FD6A00A2C715 /* SPIAPOrderModel.swift */,
1BB91CC92E04FD6A00A2C715 /* SPIAPVerifyModel.swift */,
1BB91CCA2E04FD6A00A2C715 /* SPWaitRestoreModel.swift */,
1B222BCE2E2B80DD002F5A68 /* SPPayTemplateRequest.swift */,
);
path = SPIAPManager;
sourceTree = "<group>";
@ -1266,6 +1294,7 @@
1BB91CD12E04FD6A00A2C715 /* Libs */ = {
isa = PBXGroup;
children = (
1BDE20112E1E158400C2C2B5 /* AdManager */,
1BB91CB22E04FD6A00A2C715 /* Alert */,
1BB91CB42E04FD6A00A2C715 /* APPTool */,
1BB91CB62E04FD6A00A2C715 /* Cache */,
@ -1458,6 +1487,49 @@
path = NotificationService;
sourceTree = "<group>";
};
1BDE20112E1E158400C2C2B5 /* AdManager */ = {
isa = PBXGroup;
children = (
1BF513172E20DC63009750EA /* BannerAd */,
1BF513122E1FB897009750EA /* AppOpenAd */,
1BF5130F2E1F5EE4009750EA /* RewardedAd */,
1BDE20122E1E159B00C2C2B5 /* SPAdManager.swift */,
1BDE20182E1E175800C2C2B5 /* SPAdInfo.swift */,
1BF513102E1FA138009750EA /* SPStatAdModel.swift */,
);
path = AdManager;
sourceTree = "<group>";
};
1BF5130F2E1F5EE4009750EA /* RewardedAd */ = {
isa = PBXGroup;
children = (
1BF5130D2E1F5D8F009750EA /* SPRewardedAdManager.swift */,
1BDE201D2E1E384100C2C2B5 /* SPRewardedAdManager+Admob.swift */,
1BF5130B2E1F4654009750EA /* SPRewardedAdManager+AppLovin.swift */,
);
path = RewardedAd;
sourceTree = "<group>";
};
1BF513122E1FB897009750EA /* AppOpenAd */ = {
isa = PBXGroup;
children = (
1BF513132E1FB8C1009750EA /* SPAppOpenAdManager.swift */,
1BF5131A2E265880009750EA /* SPAdmobAppOpenAd.swift */,
1BF5131C2E265A80009750EA /* SPApplovinAppOpenAd.swift */,
);
path = AppOpenAd;
sourceTree = "<group>";
};
1BF513172E20DC63009750EA /* BannerAd */ = {
isa = PBXGroup;
children = (
1BF513182E20DC85009750EA /* SPBannerAdManager.swift */,
1BF513202E2662DC009750EA /* SPAdmobBannerAd.swift */,
1BF513222E273479009750EA /* SPApplovinBannerAd.swift */,
);
path = BannerAd;
sourceTree = "<group>";
};
1DBC40502DA4EDFC0093FCB0 = {
isa = PBXGroup;
children = (
@ -1502,8 +1574,6 @@
dependencies = (
);
name = NotificationService;
packageProductDependencies = (
);
productName = NotificationService;
productReference = 1BC1F0DC2E0D268400B579A4 /* NotificationService.appex */;
productType = "com.apple.product-type.app-extension";
@ -1518,6 +1588,7 @@
1DBC40572DA4EDFC0093FCB0 /* Resources */,
4E1CBF3F1205E28DFCF11722 /* [CP] Embed Pods Frameworks */,
1BB9206E2E050B1A00A2C715 /* Embed Foundation Extensions */,
D05FE8FEDBB2F36A4EF15C23 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -1637,6 +1708,27 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
D05FE8FEDBB2F36A4EF15C23 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ThimraTV/Pods-ThimraTV-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-ThimraTV/Pods-ThimraTV-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ThimraTV/Pods-ThimraTV-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -1658,6 +1750,7 @@
1BB91D132E04FD6A00A2C715 /* AppDelegate+OpenApp.swift in Sources */,
1BB91D142E04FD6A00A2C715 /* AppDelegate+Thirdparty.swift in Sources */,
1BB91D152E04FD6A00A2C715 /* SceneDelegate.swift in Sources */,
1BF5131D2E265A80009750EA /* SPApplovinAppOpenAd.swift in Sources */,
1BB91D162E04FD6A00A2C715 /* SPNavigationController.swift in Sources */,
1BB91D172E04FD6A00A2C715 /* SPTabBarController.swift in Sources */,
1BB91D182E04FD6A00A2C715 /* SPViewController.swift in Sources */,
@ -1691,6 +1784,7 @@
1BC1F0D72E0A35EF00B579A4 /* SPVideoRevolutionManager.swift in Sources */,
1BB91D342E04FD6A00A2C715 /* SPSettingAPI.swift in Sources */,
1BB91D352E04FD6A00A2C715 /* SPStatAPI.swift in Sources */,
1BF5131B2E265880009750EA /* SPAdmobAppOpenAd.swift in Sources */,
1BB91D362E04FD6A00A2C715 /* SPUserAPI.swift in Sources */,
1BB91D372E04FD6A00A2C715 /* SPVideoAPI.swift in Sources */,
1BB91D382E04FD6A00A2C715 /* SPWalletAPI.swift in Sources */,
@ -1713,6 +1807,8 @@
1BB91D492E04FD6A00A2C715 /* SPTextField.swift in Sources */,
1BB91D4A2E04FD6A00A2C715 /* SPCampaignWebViewController.swift in Sources */,
1BB91D4B2E04FD6A00A2C715 /* SPWebMessageModel.swift in Sources */,
1BF513142E1FB8C1009750EA /* SPAppOpenAdManager.swift in Sources */,
1BDE20192E1E175800C2C2B5 /* SPAdInfo.swift in Sources */,
1BB91D4C2E04FD6A00A2C715 /* SPWebView.swift in Sources */,
1BB91D4D2E04FD6A00A2C715 /* SPWebViewController.swift in Sources */,
1BB91D4E2E04FD6A00A2C715 /* SPWebViewController+ScriptMessage.swift in Sources */,
@ -1729,6 +1825,8 @@
1BB91D592E04FD6A00A2C715 /* SPHomeChildController.swift in Sources */,
1BB91D5A2E04FD6A00A2C715 /* SPHomePageController.swift in Sources */,
1BB91D5B2E04FD6A00A2C715 /* SPHomeV2ViewController.swift in Sources */,
1BDE201E2E1E3D3E00C2C2B5 /* SPRewardedAdManager+Admob.swift in Sources */,
1BF513212E2662DC009750EA /* SPAdmobBannerAd.swift in Sources */,
1BB91D5C2E04FD6A00A2C715 /* SPHomeViewController.swift in Sources */,
1BB91D5D2E04FD6A00A2C715 /* SPSearchViewController.swift in Sources */,
1BB91D5E2E04FD6A00A2C715 /* SPHomeCategoryModel.swift in Sources */,
@ -1747,12 +1845,16 @@
1BB91D6B2E04FD6A00A2C715 /* SPHomeHeaderView.swift in Sources */,
1BB91D6C2E04FD6A00A2C715 /* SPHomeHistoryContentCell.swift in Sources */,
1BB91D6D2E04FD6A00A2C715 /* SPHomeHotCell.swift in Sources */,
1BF513112E1FA138009750EA /* SPStatAdModel.swift in Sources */,
1BB91D6E2E04FD6A00A2C715 /* SPHomeHotContentCell.swift in Sources */,
1BB91D6F2E04FD6A00A2C715 /* SPHomeHotSearchCell.swift in Sources */,
1BB91D702E04FD6A00A2C715 /* SPHomeHotSearchView.swift in Sources */,
1BF5130C2E1F4660009750EA /* SPRewardedAdManager+AppLovin.swift in Sources */,
1BB91D712E04FD6A00A2C715 /* SPHomeHotView.swift in Sources */,
1BB91D722E04FD6A00A2C715 /* SPHomeNineSquareContentCell.swift in Sources */,
1BF513192E20DC85009750EA /* SPBannerAdManager.swift in Sources */,
1BB91D732E04FD6A00A2C715 /* SPHomePlayHistoricalView.swift in Sources */,
1BDE20172E1E164600C2C2B5 /* SPAdAPI.swift in Sources */,
1BB91D742E04FD6A00A2C715 /* SPHomePlayHistoryCell.swift in Sources */,
1BB91D752E04FD6A00A2C715 /* SPHomePlayHistoryView.swift in Sources */,
1BB91D762E04FD6A00A2C715 /* SPHomeSearchButton.swift in Sources */,
@ -1775,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 */,
@ -1794,6 +1897,7 @@
1BB91D9A2E04FD6A00A2C715 /* SPMineMemberYesView.swift in Sources */,
1BB91D9B2E04FD6A00A2C715 /* SPMinePlayHistoryCell.swift in Sources */,
1BB91D9C2E04FD6A00A2C715 /* SPMinePlayHistoryView.swift in Sources */,
1BDE20202E1E669600C2C2B5 /* AttributedString+SPAdd.swift in Sources */,
1BB91D9D2E04FD6A00A2C715 /* SPMineWalletView.swift in Sources */,
1BB91D9E2E04FD6A00A2C715 /* SPSettingsCell.swift in Sources */,
1BB91D9F2E04FD6A00A2C715 /* SPCollectListViewController.swift in Sources */,
@ -1825,8 +1929,10 @@
1BB91DB92E04FD6A00A2C715 /* SPSpeedSelectedView.swift in Sources */,
1BB91DBA2E04FD6A00A2C715 /* SPPlayerListViewModel.swift in Sources */,
1BB91DBB2E04FD6A00A2C715 /* SPRewardsViewController.swift in Sources */,
1BDE20132E1E15A700C2C2B5 /* SPAdManager.swift in Sources */,
1BB91DBC2E04FD6A00A2C715 /* SPCoinOrderRecordViewController.swift in Sources */,
1BB91DBD2E04FD6A00A2C715 /* SPConsumptionRecordsViewController.swift in Sources */,
1BF5130E2E1F5D9B009750EA /* SPRewardedAdManager.swift in Sources */,
1BB91DBE2E04FD6A00A2C715 /* SPOrderRecordsPageViewController.swift in Sources */,
1BB91DBF2E04FD6A00A2C715 /* SPRewardCoinsViewController.swift in Sources */,
1BB91DC02E04FD6A00A2C715 /* SPStoreViewController.swift in Sources */,
@ -1869,6 +1975,7 @@
1BB91DE42E04FD6A00A2C715 /* SPTokenModel.swift in Sources */,
1BB91DE52E04FD6A00A2C715 /* SPPlayer.swift in Sources */,
1BB91DE62E04FD6A00A2C715 /* SPIAPManager.swift in Sources */,
1BF513232E273482009750EA /* SPApplovinBannerAd.swift in Sources */,
1BB91DE72E04FD6A00A2C715 /* SPIAPOrderModel.swift in Sources */,
1BB91DE82E04FD6A00A2C715 /* SPIAPVerifyModel.swift in Sources */,
1BB91DE92E04FD6A00A2C715 /* SPWaitRestoreModel.swift in Sources */,
@ -1904,6 +2011,7 @@
1BB91E062E04FD6A00A2C715 /* UIViewController+WMPageController.m in Sources */,
1BB91E072E04FD6A00A2C715 /* WMPageController.m in Sources */,
1BB91E082E04FD6A00A2C715 /* ZKCycleScrollView.swift in Sources */,
1BF513162E20ADB4009750EA /* SPAppOpenAdViewController.swift in Sources */,
1BB91E092E04FD6A00A2C715 /* ZKCycleScrollViewFlowLayout.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -2002,7 +2110,7 @@
CODE_SIGN_ENTITLEMENTS = ThimraTV/ThimraTV.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = TWDZ3MP9DV;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
@ -2026,7 +2134,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.1.2;
MARKETING_VERSION = 1.1.3;
PRODUCT_BUNDLE_IDENTIFIER = com.thimratv.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -2050,7 +2158,7 @@
CODE_SIGN_ENTITLEMENTS = ThimraTV/ThimraTV.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_TEAM = TWDZ3MP9DV;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GENERATE_INFOPLIST_FILE = YES;
@ -2074,7 +2182,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.1.2;
MARKETING_VERSION = 1.1.3;
PRODUCT_BUNDLE_IDENTIFIER = com.thimratv.app;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -12,8 +12,13 @@ import FirebaseCore
extension AppDelegate {
///
static var haveBeenShownAPNS = false
///
static var isRequestAuthorization = false
func registerAPNS() {
guard !Self.isRequestAuthorization else { return }
Self.isRequestAuthorization = true
FirebaseApp.configure()
Messaging.messaging().delegate = self

View File

@ -18,6 +18,8 @@ extension AppDelegate {
#if canImport(FacebookCore)
ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
#endif
///广sdk
SPAdManager.manager.start()
registAdjust()
///

View File

@ -30,7 +30,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// SPLoginManager.manager.requestVisitorLogin(completer: nil)
SPLoginManager.manager.updateUserInfo(completer: nil)
///
registerAPNS()
// registerAPNS()
return true
}
@ -54,6 +54,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if SPNetworkReachabilityManager.manager.isReachable == true {
// SPLoginManager.manager.requestVisitorLogin(completer: nil)
SPLoginManager.manager.updateUserInfo(completer: nil)
SPRewardedAdManager.manager.preloadRewardedAd()
}
}

View File

@ -110,22 +110,32 @@ extension SceneDelegate {
}
private func setRootVC() {
///
let hasOpenApp = UserDefaults.standard.object(forKey: kSPHasBeenOpenedAPPDefaultsKey) as? Bool
///
let guideVc = SPGuideViewController()
if hasOpenApp != true && guideVc.lanuchVC != nil {
SPAPPTool.isAppOpen = false
guideVc.openAppBlock = {
self.handleOpenApp()
guideVc.openAppBlock = { [weak self] in
self?.handleOpenApp()
}
window?.rootViewController = guideVc
window?.makeKeyAndVisible()
SPAPPTool.appDelegate?.registerAPNS()
} else if SPLoginManager.manager.userInfo?.user_level == .ad, !SPAPPTool.isAppOpen, hasOpenApp == true, SPNetworkReachabilityManager.manager.isReachable == true { //广
let openAdVC = SPAppOpenAdViewController()
openAdVC.didEndBlock = { [weak self] in
self?.handleOpenApp()
SPAPPTool.appDelegate?.registerAPNS()
}
window?.rootViewController = openAdVC
window?.makeKeyAndVisible()
} else {
SPAPPTool.isAppOpen = true
setTabBarController()
handleOpenApp()
SPAPPTool.appDelegate?.registerAPNS()
}
}
@ -139,6 +149,7 @@ extension SceneDelegate {
///app
@objc private func handleOpenApp() {
SPAPPTool.isAppOpen = true
setTabBarController()
retryHandleOpenAppMessage()

View File

@ -20,7 +20,7 @@ class SPTabBarController: UITabBarController {
let nav3 = createNavigationController(viewController: SPMyListViewController(), title: "movia_my_list".localized, image: UIImage(named: "tabbar_icon_04"), selectedImage: UIImage(named: "tabbar_icon_04_selected"))
let nav4 = createNavigationController(viewController: SPRewardsViewController(), title: "movia_rewards".localized, image: UIImage(named: "tabbar_icon_04"), selectedImage: UIImage(named: "tabbar_icon_04_selected"))
let nav4 = createNavigationController(viewController: SPRewardsViewController(), title: "movia_rewards".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_03_selected"))
let nav5 = createNavigationController(viewController: SPMineViewController(), title: "movia_profile".localized, image: UIImage(named: "tabbar_icon_05"), selectedImage: UIImage(named: "tabbar_icon_05_selected"))
@ -34,6 +34,8 @@ class SPTabBarController: UITabBarController {
SPAPPTool.requestIDFAAuthorization { idfa in
}
///广
SPRewardedAdManager.manager.preloadRewardedAd()
}

View File

@ -0,0 +1,15 @@
//
// AttributedString+SPAdd.swift
// ThimraTV
//
// Created by on 2025/7/9.
//
extension AttributedString {
static func sp_create(text: String, color: UIColor, font: UIFont) -> AttributedString {
return AttributedString.init(text, attributes: AttributeContainer([.font : font, .foregroundColor : color]))
}
}

View File

@ -0,0 +1,47 @@
//
// SPAdAPI.swift
// ThimraTV
//
// Created by on 2025/7/9.
//
import UIKit
class SPAdAPI {
///广
static func requestShowAdInfo(completer: ((_ adInfo: SPAdInfo?) -> Void)?) {
var param = SPNetworkParameters(path: "/ad/getShowAdInfo")
param.method = .get
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPAdDataModel>) in
if response.code != SPNetworkCodeSucceed || response.data?.ad?.platform_key == nil {
let adInfo = SPAdInfo()
adInfo.platform_key = .google
adInfo.ads_id = SPAdManager.manager.admob_rewardedAdUnitID
completer?(adInfo)
} else {
completer?(response.data?.ad)
}
}
}
///广
static func requestAdUnlockVideo(shortPlayId: String, videoId: String, adInfo: SPAdInfo, completer: ((_ model: SPVideoUnlockModel?) -> Void)?) {
var param = SPNetworkParameters(path: "/viewAdsUnlockVideo")
param.isLoding = true
param.parameters = [
"short_play_id" : shortPlayId,
"video_id" : videoId,
"ads_id" : adInfo.ads_id ?? "",
"ads_platform_key" : adInfo.platform_key?.rawValue ?? "",
"trans_id" : adInfo.ads_id ?? "",
]
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPVideoUnlockModel>) in
completer?(response.data)
}
}
}

View File

@ -72,4 +72,18 @@ class SPStatAPI: NSObject {
}
}
///广
static func requestStatAd(model: SPStatAdModel, completer: (() -> Void)? = nil) {
var param = SPNetworkParameters(path: "/ad/history")
param.isToast = false
param.isLoding = false
param.parameters = model.toDictionary()
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<String>) in
completer?()
}
}
}

View File

@ -10,13 +10,11 @@ import UIKit
class SPVideoAPI: NSObject {
///
static func requestVideoDetail(videoId: String?, shortPlayId: String, activityId: String? = nil, completer: ((_ model: SPVideoDetailModel?) -> Void)?) {
static func requestVideoDetail(videoId: String?, shortPlayId: String, activityId: String? = nil, completer: ((_ model: SPVideoDetailModel?, _ code: Int?, _ msg: String?) -> Void)?) {
var parameters: [String : Any] = [
"short_play_id" : shortPlayId
]
// if let videoId = videoId {
// }
parameters["video_id"] = "0"
if let activityId = activityId {
parameters["activity_id"] = activityId
@ -25,10 +23,20 @@ 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
completer?(response.data)
if response.code == SPNetworkCodeSucceed {
} else if response.code == 10014 {
SPToast.show(text: "movia_no_short_found".localized)
} else {
SPToast.show(text: response.msg)
}
completer?(response.data, response.code, response.msg)
}
}

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

@ -121,10 +121,11 @@ class SPNetwork: NSObject {
DispatchQueue.global().async {
let response: SPNetworkResponse<T> = _deserialize(data: tempData)
var response: SPNetworkResponse<T> = _deserialize(data: tempData)
DispatchQueue.main.async {
if response.code != SPNetworkCodeSucceed {
response.data = nil
if parameters.isToast {
SPToast.show(text: response.msg)
}

View File

@ -11,38 +11,14 @@ import Combine
import Alamofire
class SPNetworkReachabilityManager {
// enum Status {
// case notReachable
// case reachableViaWiFi
// case reachableViaWWAN
// case ethernet
// }
static let manager: SPNetworkReachabilityManager = SPNetworkReachabilityManager()
///
var isReachable: Bool?
/*
private let reachabilityManager = NetworkReachabilityManager()
func startMonitoring() {
reachabilityManager?.startListening(onUpdatePerforming: { status in
switch status {
case .notReachable:
print("网络不可用")
case .unknown:
print("网络状态未知")
case .reachable(.cellular):
print("蜂窝网络连接")
case .reachable(.ethernetOrWiFi):
print("WiFi 或有线网络连接")
}
})
}
*/
private(set) var connectionType: NWInterface.InterfaceType?
private(set) var status: NWPath.Status?
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "NetworkMonitorQueue")
@ -51,9 +27,24 @@ class SPNetworkReachabilityManager {
monitor.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
if path.status == .satisfied {
self.status = path.status
if path.usesInterfaceType(.wifi) {
self.connectionType = .wifi
spLog(message: "+++++++++++++++网络变化==wifi")
} else if path.usesInterfaceType(.cellular) {
self.connectionType = .cellular
spLog(message: "+++++++++++++++网络变化==cellular")
} else if path.usesInterfaceType(.wiredEthernet) {
self.connectionType = .wiredEthernet
spLog(message: "+++++++++++++++网络变化==wiredEthernet")
} else {
self.connectionType = nil
spLog(message: "+++++++++++++++网络变化==???")
}
if path.status == .satisfied, self.connectionType != nil {
if self.isReachable == false {
print("++++++有网")
self.isReachable = true
DispatchQueue.main.async {
NotificationCenter.default.post(name: SPNetworkReachabilityManager.reachabilityDidChangeNotification, object: nil)
@ -64,7 +55,6 @@ class SPNetworkReachabilityManager {
} else {
if self.isReachable == true {
print("++++++无网")
self.isReachable = false
DispatchQueue.main.async {
NotificationCenter.default.post(name: SPNetworkReachabilityManager.reachabilityDidChangeNotification, object: nil)
@ -73,14 +63,6 @@ class SPNetworkReachabilityManager {
self.isReachable = false
}
}
// if path.usesInterfaceType(.wifi) {
// print("++++++Using Wi-Fi")
// }
//
// if path.usesInterfaceType(.cellular) {
// print("++++++Using Cellular")
// }
}
monitor.start(queue: queue)

View File

@ -17,6 +17,7 @@ class SPWebView: WKWebView {
WebMessageOpenFeedbackList,
WebMessageOpenFeedbackDetail,
WebMessageOpenPhotoPicker,
WebMessageOpenCheckSignIn,
]
@ -62,8 +63,8 @@ class SPWebView: WKWebView {
func load(urlStr: String) {
guard let url = URL(string: urlStr) else { return }
// var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
var request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 30)
// let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 30)
self.load(request)
}

View File

@ -18,6 +18,8 @@ let WebMessageOpenFeedbackList: SPWebViewMessageName = "openFeedbackList"
let WebMessageOpenFeedbackDetail: SPWebViewMessageName = "openFeedbackDetail"
///
let WebMessageOpenPhotoPicker: SPWebViewMessageName = "openPhotoPicker"
///
let WebMessageOpenCheckSignIn: SPWebViewMessageName = "openCheckSignIn"
extension SPWebViewController {
@ -73,8 +75,19 @@ extension SPWebViewController {
}
}
} else if name == WebMessageOpenCheckSignIn { //
guard SPRewardedAdManager.manager.isAdAvailable() else {
SPRewardedAdManager.manager.showToast()
return
}
self.needAutoRefresh = false
let manager = SPRewardedAdManager.manager
manager.statScene = .reward
manager.delegate = self
manager.loadAndShowRewardedAd()
}
@ -117,3 +130,26 @@ extension SPWebViewController: TZImagePickerControllerDelegate {
}
}
//MARK: -------------- SPRewardedAdManagerDelegate --------------
extension SPWebViewController: SPRewardedAdManagerDelegate {
func rewardedAdManager(manager: SPRewardedAdManager, didLoadFail error: any Error) {
self.needAutoRefresh = true
SPRewardedAdManager.manager.delegate = nil
}
func rewardedAdManager(manager: SPRewardedAdManager, didDisplayFail error: any Error) {
self.needAutoRefresh = true
SPRewardedAdManager.manager.delegate = nil
}
func rewardedAdManager(manager: SPRewardedAdManager, didDismiss adInfo: SPAdInfo) {
self.needAutoRefresh = true
let js = "uploadCheckSignIn()"
self.webView.evaluateJavaScript(js)
SPRewardedAdManager.manager.delegate = nil
}
}

View File

@ -15,6 +15,8 @@ class SPWebViewController: SPViewController {
///
var autoTitle = true
var needAutoRefresh = true
private(set) lazy var webView: SPWebView = {
let controller = WKUserContentController()
@ -51,10 +53,7 @@ class SPWebViewController: SPViewController {
func load(urlString: String) {
let str: String = urlString
guard let url = URL(string: str) else { return }
let request = URLRequest(url: url, timeoutInterval: 30)
self.webView.load(request)
self.webView.load(urlStr: str)
}
func reload() {

View File

@ -0,0 +1,59 @@
//
// SPAppOpenAdViewController.swift
// ThimraTV
//
// Created by on 2025/7/11.
//
import UIKit
class SPAppOpenAdViewController: SPViewController {
var didEndBlock: (() -> Void)?
private(set) lazy var lanuchVC: UIViewController? = {
let vc = SPAPPTool.getLanuchViewController()
return vc
}()
override func viewDidLoad() {
super.viewDidLoad()
if let vc = lanuchVC {
addChild(vc)
view.addSubview(vc.view)
}
let manager = SPAppOpenAdManager.manager
manager.delegate = self
manager.showAdIfAvailable()
}
private func openApp() {
self.didEndBlock?()
NotificationCenter.default.post(name: SPGuideViewController.didOpenAppNotification, object: nil, userInfo: nil)
}
}
extension SPAppOpenAdViewController: SPAppOpenAdManagerDelegate {
///广
func appOpenAdManager(manager: SPAppOpenAdManager, didLoadFail error: Error) {
openApp()
}
///广
func appOpenAdManager(manager: SPAppOpenAdManager, didDisplayFail error: Error) {
openApp()
}
///广
func appOpenAdManagerDidDismiss(manager: SPAppOpenAdManager) {
openApp()
}
func appOpenAdManager(manager: SPAppOpenAdManager, didOtherFail error: any Error) {
openApp()
}
}

View File

@ -24,7 +24,12 @@ class SPMineViewController: SPViewController {
///
private var isHaveEntered = false
///广
private var needShowRewardedAd: Bool = false
weak var vipAlertView: SPVipAlertView?
private var payTemplateRequest: SPPayTemplateRequest?
//MARK: UI 
private lazy var headerView: SPMineHeaderView = {
let view = SPMineHeaderView()
@ -69,8 +74,10 @@ class SPMineViewController: SPViewController {
isHaveEntered = true
self.showRewardedAd()
///VIP
showVipAlert()
}
private func updateHeaderView() {
@ -92,10 +99,14 @@ class SPMineViewController: SPViewController {
extension SPMineViewController {
private func showVipAlert() {
guard SPLoginManager.manager.userInfo?.user_level != .ad else { return }
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 }
@ -108,6 +119,20 @@ extension SPMineViewController {
}
}
private func showRewardedAd() {
guard SPLoginManager.manager.userInfo?.user_level == .ad else { return }
guard needShowRewardedAd else { return }
needShowRewardedAd = false
guard SPRewardedAdManager.manager.isAdAvailable() else {
return
}
let manager = SPRewardedAdManager.manager
manager.delegate = nil
manager.statScene = .me
manager.loadAndShowRewardedAd(isShowToast: false)
}
}
extension SPMineViewController {
@ -138,6 +163,8 @@ extension SPMineViewController: UITableViewDelegate, UITableViewDataSource {
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.needShowRewardedAd = true
let item = dataArr[indexPath.row]
switch item.type {
case .privacyPolicy:

View File

@ -17,9 +17,18 @@ class SPVersionUpdateModel: SPModel, SmartCodable {
func canUpdate() -> Bool {
let currentCode = NSNumber(string: kSPAPPBundleVersion)?.intValue ?? 0
let serverCode = NSNumber(string: version_code ?? "0")?.intValue ?? 0
return serverCode > currentCode
// let currentCode = NSNumber(string: kSPAPPBundleVersion)?.intValue ?? 0
// let serverCode = NSNumber(string: version_code ?? "0")?.intValue ?? 0
// return serverCode > currentCode
guard let versionName = version_name else { return false }
let result = kSPAPPVersion.compare(versionName, options: .numeric)
if result == .orderedAscending {
return true
} else {
return false
}
}

View File

@ -13,13 +13,18 @@ class SPMineHeaderView: UIView {
var height: CGFloat = kSPStatusbarHeight + 100
var stackHeight = 0.0
stackHeight += memberView.intrinsicContentSize.height
stackHeight += self.stackView.spacing
stackHeight += walletView.intrinsicContentSize.height
if userInfo?.user_level != .ad {
stackHeight += memberView.intrinsicContentSize.height
stackHeight += self.stackView.spacing
stackHeight += walletView.intrinsicContentSize.height
}
if playHistoryArr?.count ?? 0 > 0 {
stackHeight += self.stackView.spacing
if stackHeight > 0 {
stackHeight += self.stackView.spacing
}
stackHeight += playHistoryView.contentHeight
}
@ -49,6 +54,8 @@ class SPMineHeaderView: UIView {
memberView.userInfo = self.userInfo
walletView.userInfo = self.userInfo
updateStackViewLayout()
}
}
@ -150,9 +157,15 @@ class SPMineHeaderView: UIView {
private func updateStackViewLayout() {
stackView.removeAllArrangedSubview()
stackView.addArrangedSubview(memberView)
stackView.addArrangedSubview(walletView)
if userInfo?.user_level != .ad {
stackView.addArrangedSubview(memberView)
stackView.addArrangedSubview(walletView)
loginButton.isHidden = false
} else {
loginButton.isHidden = true
}
if let arr = playHistoryArr, arr.count > 0 {
stackView.addArrangedSubview(playHistoryView)

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,9 +162,21 @@ 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()
if SPLoginManager.manager.userInfo?.user_level == .ad {
let manager = SPRewardedAdManager.manager
manager.delegate = nil
manager.statScene = .detail
manager.videoInfo = self.viewModel.currentPlayer?.videoInfo
if manager.isAdAvailable() {
manager.loadAndShowRewardedAd()
}
}
}
view.clickPlayButton = { [weak self] model in
@ -235,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
@ -253,42 +271,31 @@ extension SPPlayerDetailViewController {
///
private func unlockVideo(indexPath: IndexPath) {
guard let videoInfo = detailModel?.episodeList?[indexPath.row] else { return }
guard let shortPlayId = videoInfo.short_play_id, let videoId = videoInfo.short_play_video_id else { return }
SPWalletAPI.requestCoinUnlockVideo(shortPlayId: shortPlayId, videoId: videoId) { [weak self] model in
self.viewModel.unlockVideo { [weak self] finish in
guard let self = self else { return }
guard let model = model else { return }
switch model.status {
case .jump:
SPToast.show(text: "movia_jump_unlock_error".localized)
case .noPlay:
SPToast.show(text: "movia_buy_fail_toast_01".localized)
case .notEnough:
self.onPlayBuy()
case .success:
videoInfo.is_lock = false
self.reloadData { [weak self] in
guard let self = self else { return }
self.play()
}
//
SPLoginManager.manager.updateUserInfo(completer: nil)
default:
break
if finish {
self.reloadData { [weak self] in
guard let self = self else { return }
self.play()
}
}
}
}
///广
private func adUnlockVideo() {
self.viewModel.adUnlockVideo { [weak self] in
guard let self = self else { return }
self.reloadData { [weak self] in
guard let self = self else { return }
self.play()
}
}
}
///
private func uploadPlayTime() {
let videoInfo = self.viewModel.currentPlayer?.videoInfo
@ -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]
@ -362,20 +371,49 @@ extension SPPlayerDetailViewController: SPPlayerListViewControllerDataSource, SP
self.unlockVideo(indexPath: indexPath)
}
cell.clickAdUnlockButton = { [weak self] cell in
self?.adUnlockVideo()
}
}
return oldCell
}
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]
titleLabel.text = detailModel?.shortPlayInfo?.name
episodeLabel.text = "\(videoInfo?.episode ?? "0")/\(detailModel?.shortPlayInfo?.episode_total ?? 0)"
// 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) {
}
}
@ -398,33 +436,42 @@ extension SPPlayerDetailViewController {
recommandTimer = nil
recommandTimer = Timer.scheduledTimer(timeInterval: 6, target: YYWeakProxy(target: self), selector: #selector(handleRecommandTimer), userInfo: nil, repeats: false)
SPVideoAPI.requestVideoDetail(videoId: videoId, shortPlayId: shortPlayId, activityId: activityId) { [weak self] model in
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 let model = model {
self.detailModel = model
self.reloadData { [weak self] in
guard let self = self else { return }
if code == 10014 {
self.navigationController?.popViewController(animated: true)
return
}
guard let model = model else { return }
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)
if let indexPath = indexPath, indexPath.row < (model.episodeList?.count ?? 0) {
self.scrollToItem(indexPath: indexPath, animated: false)
} else if let videoInfo = self.detailModel?.video_info {
var row: Int?
self.detailModel?.episodeList?.enumerated().forEach({
if $1.id == videoInfo.id {
row = $0
}
})
if let row = row {
self.scrollToItem(indexPath: .init(row: row, section: 0), animated: false)
} else {
self.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false)
} else if let videoInfo = model.video_info {
var row: Int?
model.episodeList?.enumerated().forEach({
if $1.id == videoInfo.id {
row = $0
}
})
if let row = row {
self.scrollToItem(indexPath: .init(row: row, section: 0), animated: false)
} else {
self.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false)
}
} else {
self.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false)
}
}
}
}

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()
@ -75,8 +71,6 @@ class SPPlayerListViewController: SPViewController {
private(set) lazy var collectionView: SPCollectionView = {
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isPagingEnabled = true
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
@ -88,6 +82,9 @@ class SPPlayerListViewController: SPViewController {
deinit {
NotificationCenter.default.removeObserver(self)
self.collectionView.delegate = nil
self.collectionView.dataSource = nil
self.collectionView.removeFromSuperview()
}
override func viewDidLoad() {
@ -101,6 +98,8 @@ class SPPlayerListViewController: SPViewController {
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActiveNotification), name: UIApplication.didBecomeActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willResignActiveNotification), name: UIApplication.willResignActiveNotification, object: nil)
collectionView.delegate = self
collectionView.dataSource = self
sp_setupUI()
sp_addActio()
@ -170,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() {
@ -192,20 +181,23 @@ class SPPlayerListViewController: SPViewController {
}
func reloadData(completion: (() -> Void)? = nil) {
CATransaction.setCompletionBlock { [weak self] in
UIView.performWithoutAnimation {
self.collectionView.reloadData()
}
self.collectionView.performBatchUpdates(nil) { [weak self] _ in
guard let self = self else { return }
let cell = self.collectionView.cellForItem(at: self.viewModel.currentIndexPath) as? SPPlayerListCell
self.viewModel.currentPlayer = cell
completion?()
}
CATransaction.begin()
self.collectionView.reloadData()
CATransaction.commit()
}
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) {
@ -227,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()
}
}
///
@ -351,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)
@ -410,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

@ -9,6 +9,12 @@ import UIKit
class SPPlayLockView: UIView {
///
var clickUnlockButton: (() -> Void)?
///广
var clickAdUnlockButton: (() -> Void)?
///
var isUnlockUpEpisode: Bool = false {
didSet {
@ -18,12 +24,22 @@ class SPPlayLockView: UIView {
var videoInfo: SPVideoInfoModel? {
didSet {
coinView.setTitle("\(videoInfo?.coins ?? 0)", for: .normal)
let userInfo = SPLoginManager.manager.userInfo
let coins = videoInfo?.coins ?? 0
let myCoins = (userInfo?.coin_left_total ?? 0) + (userInfo?.send_coin_left_total ?? 0)
coinView.setTitle("\(coins)", for: .normal)
if SPLoginManager.manager.userInfo?.user_level == .ad && (coins == 0 || myCoins < coins) {
adUnlockButton.isHidden = false
unlockButton.isHidden = true
} else {
adUnlockButton.isHidden = true
unlockButton.isHidden = false
}
}
}
///
var clickUnlockButton: (() -> Void)?
//MARK: UI
private lazy var containerView: UIView = {
@ -91,12 +107,38 @@ class SPPlayLockView: UIView {
return view
}()
private lazy var adUnlockButton: UIButton = {
var config = UIButton.Configuration.plain()
config.image = UIImage(named: "video_icon_01")
config.imagePadding = 6
let button = UIButton(configuration: config)
button.isHidden = true
button.layer.cornerRadius = 27
button.layer.masksToBounds = true
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.colorFFFFFF().cgColor
button.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
if isUnlockUpEpisode {
button.configuration?.attributedTitle = AttributedString.sp_create(text: "movia_video_lock_tip_02".localized, color: .colorFFFFFF(), font: .fontMedium(ofSize: 16))
} else {
button.configuration?.attributedTitle = AttributedString.sp_create(text: "movia_video_lock_tip_03".localized, color: .colorFFFFFF(), font: .fontMedium(ofSize: 16))
}
}
button.addTarget(self, action: #selector(handleAdUnlockButton), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .color000000(alpha: 0.75)
updateUnlockButton()
_setupUI()
}
@ -115,6 +157,15 @@ class SPPlayLockView: UIView {
}
@objc private func handleAdUnlockButton() {
if isUnlockUpEpisode {
SPToast.show(text: "movia_jump_unlock_error".localized)
return
}
self.clickAdUnlockButton?()
}
private func updateUnlockButton() {
unlockStackView.removeAllArrangedSubview()
@ -129,6 +180,8 @@ class SPPlayLockView: UIView {
unlockStackView.addArrangedSubview(coinView)
}
adUnlockButton.setNeedsUpdateConfiguration()
}
}
@ -141,6 +194,7 @@ extension SPPlayLockView {
containerView.addSubview(lockTextLabel)
containerView.addSubview(unlockButton)
unlockButton.addSubview(unlockStackView)
containerView.addSubview(adUnlockButton)
containerView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
@ -170,6 +224,9 @@ extension SPPlayLockView {
make.centerX.equalToSuperview()
}
adUnlockButton.snp.makeConstraints { make in
make.edges.equalTo(unlockButton)
}
}
}

View File

@ -15,6 +15,8 @@ class SPPlayerDetailCell: SPPlayerListCell {
///
var clickUnlockButton: ((_ cell: SPPlayerDetailCell) -> Void)?
///广
var clickAdUnlockButton: ((_ cell: SPPlayerDetailCell) -> Void)?
///
var hasLockUpEpisode = false {
@ -32,6 +34,11 @@ class SPPlayerDetailCell: SPPlayerListCell {
self.clickUnlockButton?(self)
}
controlView.clickAdUnlockButton = { [weak self] in
guard let self = self else { return }
self.clickAdUnlockButton?(self)
}
}
}

View File

@ -92,6 +92,8 @@ class SPPlayerDetailControlView: SPPlayerControlView {
///
var clickUnlockButton: (() -> Void)?
///广
var clickAdUnlockButton: (() -> Void)?
///
private var timer: Timer?
@ -150,6 +152,9 @@ class SPPlayerDetailControlView: SPPlayerControlView {
view.clickUnlockButton = { [weak self] in
self?.clickUnlockButton?()
}
view.clickAdUnlockButton = { [weak self] in
self?.clickAdUnlockButton?()
}
return view
}()

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? {
@ -28,6 +36,11 @@ class SPPlayerDetailRecommandView: HWPanModalContentView {
}
}
private lazy var bannerAd: SPBannerAdManager = {
let ad = SPBannerAdManager()
return ad
}()
//MARK: UI
private lazy var bgImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "recommand_bg_image_01"))
@ -102,7 +115,11 @@ class SPPlayerDetailRecommandView: HWPanModalContentView {
//MARK: HWPanModalPresentable
override func longFormHeight() -> PanModalHeight {
return PanModalHeightMake(.content, 540 + kSPTabbarSafeBottomMargin)
if SPLoginManager.manager.userInfo?.user_level == .ad {
return PanModalHeightMake(.content, 540 + bannerAd.size.height + kSPTabbarSafeBottomMargin)
} else {
return PanModalHeightMake(.content, 540 + kSPTabbarSafeBottomMargin)
}
}
override func showDragIndicator() -> Bool {
@ -136,6 +153,8 @@ class SPPlayerDetailRecommandView: HWPanModalContentView {
extension SPPlayerDetailRecommandView {
@objc private func handleCloseButton() {
self.bannerAd.requestStatAd(type: "close", errorMsg: nil)
self.dismiss(animated: true) {
}
self.clickCloseButton?()
@ -195,6 +214,18 @@ extension SPPlayerDetailRecommandView {
make.top.equalTo(bannerView.snp.bottom).offset(81)
make.height.equalTo(46)
}
if SPLoginManager.manager.userInfo?.user_level == .ad {
addSubview(bannerAd.view)
bannerAd.view.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().offset(-kSPTabbarSafeBottomMargin)
make.size.equalTo(bannerAd.size)
}
}
}
}

View File

@ -33,6 +33,9 @@ class SPPlayerListViewModel: NSObject {
@objc dynamic private(set) lazy var speedModel = SPSpeedModel(speed: .x1)
///
private var videoUnlockFinishBlock: (() -> Void)?
///
func setSpeedPlay(speedModel: SPSpeedModel) {
self.speedModel = speedModel
@ -88,7 +91,9 @@ extension SPPlayerListViewModel {
///
func onPlayBuy() {
let userInfo = SPLoginManager.manager.userInfo
guard let videoInfo = self.currentPlayer?.videoInfo else { return }
guard userInfo?.user_level != .ad else { return }
let view = SPPlayBuyView()
view.shortPlayId = videoInfo.short_play_id
@ -101,4 +106,89 @@ extension SPPlayerListViewModel {
view.present(in: nil)
}
func unlockVideo(completer: ((_ finish: Bool) -> Void)?) {
let videoInfo = self.currentPlayer?.videoInfo
guard let shortPlayId = videoInfo?.short_play_id, let videoId = videoInfo?.short_play_video_id else { return }
SPWalletAPI.requestCoinUnlockVideo(shortPlayId: shortPlayId, videoId: videoId) { [weak self] model in
guard let self = self else { return }
guard let model = model else { return }
switch model.status {
case .jump:
SPToast.show(text: "movia_jump_unlock_error".localized)
completer?(false)
case .noPlay:
SPToast.show(text: "movia_buy_fail_toast_01".localized)
completer?(false)
case .notEnough:
self.onPlayBuy()
completer?(false)
case .success:
//
SPLoginManager.manager.updateUserInfo {
videoInfo?.is_lock = false
completer?(true)
}
default:
completer?(false)
break
}
}
}
func adUnlockVideo(finish: (() -> Void)?) {
guard SPRewardedAdManager.manager.isAdAvailable() else {
SPRewardedAdManager.manager.showToast()
return
}
let manager = SPRewardedAdManager.manager
manager.delegate = self
manager.videoInfo = self.currentPlayer?.videoInfo
manager.statScene = .detail
manager.loadAndShowRewardedAd()
self.videoUnlockFinishBlock = finish
}
}
//MARK: -------------- SPAdManagerDelegate --------------
extension SPPlayerListViewModel: SPRewardedAdManagerDelegate {
func rewardedAdManager(manager: SPRewardedAdManager, didDismiss adInfo: SPAdInfo) {
manager.delegate = nil
let videoInfo = self.currentPlayer?.videoInfo
guard let shortPlayId = videoInfo?.short_play_id, let videoId = videoInfo?.short_play_video_id else { return }
SPAdAPI.requestAdUnlockVideo(shortPlayId: shortPlayId, videoId: videoId, adInfo: adInfo) { [weak self] model in
guard let self = self else { return }
if model?.status == .jump {
SPToast.show(text: "movia_jump_unlock_error".localized)
} else if model?.status == .success {
self.currentPlayer?.videoInfo?.is_lock = false
self.videoUnlockFinishBlock?()
}
}
}
func rewardedAdManager(manager: SPRewardedAdManager, didLoadFail error: any Error) {
manager.delegate = nil
}
func rewardedAdManager(manager: SPRewardedAdManager, didDisplayFail error: any Error) {
manager.delegate = nil
}
}

View File

@ -16,6 +16,7 @@ class SPRewardsViewController: SPCampaignWebViewController {
private var isFirst = true
override func viewDidLoad() {
self.urlStr = SPRewardsWebUrl
super.viewDidLoad()
@ -54,7 +55,9 @@ class SPRewardsViewController: SPCampaignWebViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !isFirst {
self.reload()
if needAutoRefresh {
self.reload()
}
} else {
isFirst = false
}

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

@ -12,7 +12,7 @@ import AdSupport
class SPAPPTool: NSObject {
///app
static var isAppOpen = true
static var isAppOpen = false
static var appDelegate: AppDelegate?
static var sceneDelegate: SceneDelegate?

View File

@ -0,0 +1,67 @@
//
// SPAdmobAppOpenAd.swift
// ThimraTV
//
// Created by on 2025/7/15.
//
import UIKit
import GoogleMobileAds
class SPAdmobAppOpenAd: NSObject, SPAppOpenAd {
private var appOpenAd: AppOpenAd?
var delegate: (any SPAppOpenAdDelegate)?
var adPlatformKey: String {
return SPAdPlatformKey.google.rawValue
}
var adUnitID: String {
return SPAdManager.manager.admob_appOpenAdUnitID
}
var isReady: Bool {
return appOpenAd != nil
}
func loadAd() {
AppOpenAd.load(with: adUnitID, request: Request()) { [weak self] appOpenAd, error in
guard let self = self else { return }
self.appOpenAd = appOpenAd
self.appOpenAd?.fullScreenContentDelegate = self
if let error = error {
self.delegate?.appOpenAd?(ad: self, didLoadFail: error)
} else {
self.delegate?.appOpenAdDidLoadFinish?(ad: self)
}
}
}
func showAd() {
appOpenAd?.present(from: nil)
}
}
extension SPAdmobAppOpenAd: FullScreenContentDelegate {
func adWillPresentFullScreenContent(_ ad: FullScreenPresentingAd) {
self.delegate?.appOpenAdDidShow?(ad: self)
}
func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) {
self.delegate?.appOpenAdDidDismiss?(ad: self)
}
func ad(_ ad: FullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
self.delegate?.appOpenAd?(ad: self, didDisplayFail: error)
}
func adDidRecordClick(_ ad: any FullScreenPresentingAd) {
self.delegate?.appOpenAdDidClick?(ad: self)
}
}

View File

@ -0,0 +1,221 @@
//
// SPAppOpenAdManager.swift
// ThimraTV
//
// Created by on 2025/7/10.
//
import UIKit
@objc protocol SPAppOpenAdManagerDelegate: NSObjectProtocol {
///广
@objc optional func appOpenAdManager(manager: SPAppOpenAdManager, didLoadFail error: Error)
///广
@objc optional func appOpenAdManagerDidLoadFinish(manager: SPAppOpenAdManager)
///广
@objc optional func appOpenAdManager(manager: SPAppOpenAdManager, didDisplayFail error: Error)
///广
@objc optional func appOpenAdManagerDidShow(manager: SPAppOpenAdManager)
///广
@objc optional func appOpenAdManagerDidDismiss(manager: SPAppOpenAdManager)
///
@objc optional func appOpenAdManager(manager: SPAppOpenAdManager, didOtherFail error: Error)
}
@objc protocol SPAppOpenAdDelegate: NSObjectProtocol {
///广
@objc optional func appOpenAd(ad: SPAppOpenAd, didLoadFail error: Error)
///广
@objc optional func appOpenAdDidLoadFinish(ad: SPAppOpenAd)
///广
@objc optional func appOpenAd(ad: SPAppOpenAd, didDisplayFail error: Error)
///广
@objc optional func appOpenAdDidShow(ad: SPAppOpenAd)
///广
@objc optional func appOpenAdDidDismiss(ad: SPAppOpenAd)
///广
@objc optional func appOpenAdDidClick(ad: SPAppOpenAd)
}
@objc protocol SPAppOpenAd: NSObjectProtocol {
weak var delegate: SPAppOpenAdDelegate? { get set }
var adPlatformKey: String { get }
var adUnitID: String { get }
var isReady: Bool { get }
func loadAd()
func showAd()
}
class SPAppOpenAdManager: NSObject {
static let manager = SPAppOpenAdManager()
weak var delegate: SPAppOpenAdManagerDelegate?
let adUnitID = SPAdManager.manager.admob_appOpenAdUnitID
private var appOpenAd: SPAppOpenAd?
private(set) var isLoadingAd = false
private(set) var isShowingAd = false
private var isNeedShow = false
private var timeOutTimer: Timer?
deinit {
NotificationCenter.default.removeObserver(self)
}
override init() {
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
private func loadAd() {
// Do not load ad if there is an unused ad or one is already loading.
if isLoadingAd || isAdAvailable() {
return
}
isLoadingAd = true
appOpenAd = SPAdmobAppOpenAd()
appOpenAd?.delegate = self
appOpenAd?.loadAd()
}
func showAd() {
if let ad = appOpenAd {
self.isNeedShow = false
isShowingAd = true
ad.showAd()
}
}
func showAdIfAvailable() {
// If the app open ad is already showing, do not show the ad again.
guard !isShowingAd else { return }
self.isNeedShow = true
self.timeOutTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(handleTimeOutTimer), userInfo: nil, repeats: false)
// If the app open ad is not available yet but is supposed to show, load
// a new ad.
if !isAdAvailable() {
self.loadAd()
return
}
showAd()
}
private func isAdAvailable() -> Bool {
return appOpenAd?.isReady ?? false
}
@objc private func handleTimeOutTimer() {
self.isNeedShow = false
self.timeOutTimer?.invalidate()
self.timeOutTimer = nil
clearTimer()
let error = NSError(domain: "time-out", code: -1)
self.delegate?.appOpenAdManager?(manager: self, didOtherFail: error)
}
private func clearTimer() {
self.timeOutTimer?.invalidate()
self.timeOutTimer = nil
}
}
//MARK: -------------- SPAppOpenAdDelegate --------------
extension SPAppOpenAdManager: SPAppOpenAdDelegate {
///广
func appOpenAd(ad: SPAppOpenAd, didLoadFail error: Error) {
self.requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
isLoadingAd = false
clearTimer()
self.delegate?.appOpenAdManager?(manager: self, didLoadFail: error)
}
///广
func appOpenAdDidLoadFinish(ad: SPAppOpenAd) {
isLoadingAd = false
clearTimer()
self.delegate?.appOpenAdManagerDidLoadFinish?(manager: self)
if isNeedShow {
self.showAd()
}
}
///广
func appOpenAd(ad: SPAppOpenAd, didDisplayFail error: Error) {
self.requestStatAd(type: "show_failed", errorMsg: error.localizedDescription)
appOpenAd = nil
isShowingAd = false
clearTimer()
self.delegate?.appOpenAdManager?(manager: self, didDisplayFail: error)
}
///广
func appOpenAdDidShow(ad: SPAppOpenAd) {
self.requestStatAd(type: "start", errorMsg: nil)
self.delegate?.appOpenAdManagerDidShow?(manager: self)
}
///广
func appOpenAdDidDismiss(ad: SPAppOpenAd) {
self.requestStatAd(type: "close", errorMsg: nil)
appOpenAd = nil
isShowingAd = false
self.delegate?.appOpenAdManagerDidDismiss?(manager: self)
}
///广
func appOpenAdDidClick(ad: SPAppOpenAd) {
self.requestStatAd(type: "click", errorMsg: nil)
}
}
//MARK: -------------- --------------
extension SPAppOpenAdManager {
private func requestStatAd(type: String, errorMsg: String?) {
guard let appOpenAd = appOpenAd else { return }
let model = SPStatAdModel()
model.type = type
model.ads_id = appOpenAd.adUnitID
model.ad_platform_key = SPAdPlatformKey(rawValue: appOpenAd.adPlatformKey)
model.error_msg = errorMsg
model.scene = .splash
SPStatAPI.requestStatAd(model: model)
}
@objc private func didEnterBackgroundNotification() {
if !self.isShowingAd { return }
self.requestStatAd(type: "Interrupt", errorMsg: nil)
}
}

View File

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

View File

@ -0,0 +1,75 @@
//
// SPAdmobBannerAd.swift
// ThimraTV
//
// Created by on 2025/7/15.
//
import UIKit
import GoogleMobileAds
class SPAdmobBannerAd: NSObject, SPBannerAd {
var delegate: (any SPBannerAdDelegate)?
let size = CGSize.init(width: kSPScreenWidth, height: 59)
private lazy var _adView: BannerView = {
let view = BannerView()
view.adUnitID = self.adUnitID
view.adSize = inlineAdaptiveBanner(width: size.width, maxHeight:size.height)
view.delegate = self
return view
}()
var adView: UIView {
return _adView
}
var adPlatformKey: String {
return SPAdPlatformKey.google.rawValue
}
var adUnitID: String {
return SPAdManager.manager.admob_bannerAdUnitID
}
func loadAd() {
_adView.load(Request())
}
}
//MARK: -------------- BannerViewDelegate --------------
extension SPAdmobBannerAd: BannerViewDelegate {
func bannerViewDidReceiveAd(_ bannerView: BannerView) {
self.delegate?.bannerAdDidLoadFinish?(bannerAd: self)
}
func bannerView(_ bannerView: BannerView, didFailToReceiveAdWithError error: Error) {
self.delegate?.bannerAd?(bannerAd: self, didLoadFail: error)
}
func bannerViewDidRecordClick(_ bannerView: BannerView) {
self.delegate?.bannerAdDidClick?(ad: self)
}
func bannerViewDidRecordImpression(_ bannerView: BannerView) {
}
func bannerViewWillPresentScreen(_ bannerView: BannerView) {
self.delegate?.bannerAdDidShow?(bannerAd: self)
}
func bannerViewWillDismissScreen(_ bannerView: BannerView) {
}
func bannerViewDidDismissScreen(_ bannerView: BannerView) {
self.delegate?.bannerAdDidDismiss?(bannerAd: self)
}
}

View File

@ -0,0 +1,91 @@
//
// SPApplovinBannerAd.swift
// ThimraTV
//
// Created by on 2025/7/16.
//
import UIKit
import AppLovinSDK
class SPApplovinBannerAd: NSObject, SPBannerAd {
let size = CGSize.init(width: kSPScreenWidth, height: 59)
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)
view.delegate = self
return view
}()
var adView: UIView {
return _adView
}
var adPlatformKey: String {
return SPAdPlatformKey.applovin.rawValue
}
var adUnitID: String {
return SPAdManager.manager.applovin_bannerAdUnitID
}
func loadAd() {
_adView.loadAd()
}
}
//MARK: -------------- MAAdViewAdDelegate --------------
extension SPApplovinBannerAd: MAAdViewAdDelegate {
func didExpand(_ ad: MAAd) {
}
func didCollapse(_ ad: MAAd) {
}
func didLoad(_ ad: MAAd) {
if !isLoaded {
isLoaded = true
self.delegate?.bannerAdDidLoadFinish?(bannerAd: self)
}
}
func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
if !isLoaded {
isLoaded = true
let nsError = NSError(domain: error.message, code: error.code.rawValue)
self.delegate?.bannerAd?(bannerAd: self, didLoadFail: nsError)
}
}
func didDisplay(_ ad: MAAd) {
self.delegate?.bannerAdDidShow?(bannerAd: self)
}
func didHide(_ ad: MAAd) {
self.delegate?.bannerAdDidDismiss?(bannerAd: self)
}
func didClick(_ ad: MAAd) {
self.delegate?.bannerAdDidClick?(ad: self)
}
func didFail(toDisplay ad: MAAd, withError error: MAError) {
}
}

View File

@ -0,0 +1,139 @@
//
// SPBannerAdManager.swift
// ThimraTV
//
// Created by on 2025/7/11.
//
import UIKit
import GoogleMobileAds
@objc protocol SPBannerAdManagerDelegate: NSObjectProtocol {
///广
@objc optional func bannerAdManager(adManager: SPBannerAdManager, didLoadFail error: Error)
///广
@objc optional func bannerAdManagerDidLoadFinish(adManager: SPBannerAdManager)
///广
@objc optional func bannerAdManagerDidShow(adManager: SPBannerAdManager)
///广
@objc optional func bannerAdManagerDidDismiss(adManager: SPBannerAdManager)
}
@objc protocol SPBannerAdDelegate: NSObjectProtocol {
///广
@objc optional func bannerAd(bannerAd: SPBannerAd, didLoadFail error: Error)
///广
@objc optional func bannerAdDidLoadFinish(bannerAd: SPBannerAd)
///广
@objc optional func bannerAdDidShow(bannerAd: SPBannerAd)
///广
@objc optional func bannerAdDidDismiss(bannerAd: SPBannerAd)
///广
@objc optional func bannerAdDidClick(ad: SPBannerAd)
}
@objc protocol SPBannerAd: NSObjectProtocol {
weak var delegate: SPBannerAdDelegate? { get set }
var adView: UIView { get }
var adPlatformKey: String { get }
var adUnitID: String { get }
func loadAd()
}
class SPBannerAdManager: NSObject {
let adUnitID = SPAdManager.manager.admob_bannerAdUnitID
let size = CGSize.init(width: kSPScreenWidth, height: 59)
weak var delegate: SPBannerAdManagerDelegate?
var videoInfo: SPVideoInfoModel?
private lazy var bannerAd: SPBannerAd = {
let ad = SPAdmobBannerAd()
ad.delegate = self
return ad
}()
var view: UIView {
return bannerAd.adView
}
deinit {
NotificationCenter.default.removeObserver(self)
}
override init() {
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil)
bannerAd.loadAd()
}
}
//MARK: -------------- SPBannerAdDelegate --------------
extension SPBannerAdManager: SPBannerAdDelegate {
///广
func bannerAd(bannerAd: SPBannerAd, didLoadFail error: Error) {
self.requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
self.delegate?.bannerAdManager?(adManager: self, didLoadFail: error)
}
///广
func bannerAdDidLoadFinish(bannerAd: SPBannerAd) {
self.requestStatAd(type: "start", errorMsg: nil)
self.delegate?.bannerAdManagerDidLoadFinish?(adManager: self)
}
///广
func bannerAdDidShow(bannerAd: SPBannerAd) {
self.delegate?.bannerAdManagerDidShow?(adManager: self)
}
///广
func bannerAdDidDismiss(bannerAd: SPBannerAd) {
self.requestStatAd(type: "close", errorMsg: nil)
self.delegate?.bannerAdManagerDidDismiss?(adManager: self)
}
func bannerAdDidClick(ad: any SPBannerAd) {
self.requestStatAd(type: "click", errorMsg: nil)
}
}
//MARK: -------------- --------------
extension SPBannerAdManager {
func requestStatAd(type: String, errorMsg: String?) {
guard self.view.superview != nil else { return }
let model = SPStatAdModel()
model.type = type
model.ads_id = adUnitID
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)
}
@objc private func didEnterBackgroundNotification() {
self.requestStatAd(type: "Interrupt", errorMsg: nil)
}
}

View File

@ -0,0 +1,141 @@
//
// SPAdManager+admob.swift
// ThimraTV
//
// Created by on 2025/7/9.
//
import UIKit
import GoogleMobileAds
extension SPRewardedAdManager {
fileprivate struct AssociatedKeys {
static var admob_rewardedAd: Int?
static var admob_needShowRewardedAd: Int?
static var admob_isLoadingRewardedAd: Int?
}
var admob_rewardedAd: RewardedAd? {
set {
objc_setAssociatedObject(self, &AssociatedKeys.admob_rewardedAd, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get {
return objc_getAssociatedObject(self, &AssociatedKeys.admob_rewardedAd) as? RewardedAd
}
}
///广
var admob_isLoadingRewardedAd: Bool {
set {
objc_setAssociatedObject(self, &AssociatedKeys.admob_isLoadingRewardedAd, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get {
return (objc_getAssociatedObject(self, &AssociatedKeys.admob_isLoadingRewardedAd) as? Bool) ?? false
}
}
///广true广广
private var admob_needShowRewardedAd: Bool {
set {
objc_setAssociatedObject(self, &AssociatedKeys.admob_needShowRewardedAd, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get {
return (objc_getAssociatedObject(self, &AssociatedKeys.admob_needShowRewardedAd) as? Bool) ?? false
}
}
func admob_isReady() -> Bool {
return admob_rewardedAd != nil ? true : false
}
///广
func admob_loadAndShowRewardedAd(adInfo: SPAdInfo) {
self.admob_needShowRewardedAd = true
if self.admob_isLoadingRewardedAd {
return
}
if self.admob_rewardedAd != nil {
self.admob_show()
return
}
admob_loadRewardedAd(adInfo: adInfo)
}
func admob_loadRewardedAd(adInfo: SPAdInfo) {
guard !self.admob_isLoadingRewardedAd else { return }
#if DEBUG
adInfo.ads_id = SPAdManager.manager.admob_rewardedAdUnitID
#endif
let adUnitID = adInfo.ads_id ?? ""
let request = Request()
self.admob_isLoadingRewardedAd = true
RewardedAd.load(with: adUnitID, request: request) { [weak self] rewardedAd, error in
guard let self = self else { return }
self.admob_isLoadingRewardedAd = false
if let error = error {
self.admob_needShowRewardedAd = false
self.loadFailHandler(error: error)
return
}
self.loadFinishHandler()
self.admob_rewardedAd = rewardedAd
self.admob_rewardedAd?.fullScreenContentDelegate = self
if self.admob_needShowRewardedAd {
self.admob_show()
}
}
}
///广
private func admob_show() {
guard let rewardedAd = admob_rewardedAd, self.admob_needShowRewardedAd else {
return
}
self.admob_needShowRewardedAd = false
// The UIViewController parameter is an optional.
rewardedAd.present(from: nil) { [weak self] in
self?.userDidEarnRewardHandler()
// TODO: Reward the user.
}
}
}
extension SPRewardedAdManager: FullScreenContentDelegate {
/// Tells the delegate that the ad failed to present full screen content.
func ad(_ ad: FullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
self.admob_rewardedAd = nil
self.admob_needShowRewardedAd = false
self.displayFailHandler(error: error)
}
/// Tells the delegate that the ad will present full screen content.
func adWillPresentFullScreenContent(_ ad: FullScreenPresentingAd) {
self.didShowHandler()
}
/// Tells the delegate that the ad dismissed full screen content.
func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) {
self.admob_rewardedAd = nil
self.didDismissHandler()
}
func adDidRecordClick(_ ad: any FullScreenPresentingAd) {
self.didClickHandler()
}
}

View File

@ -0,0 +1,155 @@
//
// SPAdManager+AppLovin.swift
// ThimraTV
//
// Created by on 2025/7/10.
//
#if canImport(AppLovinSDK)
import AppLovinSDK
#endif
extension SPRewardedAdManager {
fileprivate struct AssociatedKeys {
static var appLovin_rewardedAd: Int?
static var appLovin_needShowRewardedAd: Int?
static var appLovin_isLoadingRewardedAd: Int?
}
#if canImport(AppLovinSDK)
private var appLovin_rewardedAd: MARewardedAd? {
set {
objc_setAssociatedObject(self, &AssociatedKeys.appLovin_rewardedAd, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get {
return objc_getAssociatedObject(self, &AssociatedKeys.appLovin_rewardedAd) as? MARewardedAd
}
}
///广true广广
private var appLovin_needShowRewardedAd: Bool {
set {
objc_setAssociatedObject(self, &AssociatedKeys.appLovin_needShowRewardedAd, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get {
return (objc_getAssociatedObject(self, &AssociatedKeys.appLovin_needShowRewardedAd) as? Bool) ?? false
}
}
///广
var appLovin_isLoadingRewardedAd: Bool {
set {
objc_setAssociatedObject(self, &AssociatedKeys.appLovin_isLoadingRewardedAd, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
get {
return (objc_getAssociatedObject(self, &AssociatedKeys.appLovin_isLoadingRewardedAd) as? Bool) ?? false
}
}
#endif
func appLovin_isReady() -> Bool {
#if canImport(AppLovinSDK)
return appLovin_rewardedAd?.isReady ?? false
#else
return false
#endif
}
///广
func appLovin_loadRewardedAd(adInfo: SPAdInfo) {
#if canImport(AppLovinSDK)
if self.appLovin_isLoadingRewardedAd { return }
self.appLovin_isLoadingRewardedAd = true
self.appLovin_rewardedAd = MARewardedAd.shared(withAdUnitIdentifier: adInfo.ads_id ?? "")
self.appLovin_rewardedAd?.delegate = self
self.appLovin_rewardedAd?.load()
#endif
}
///广
func appLovin_loadAndShowRewardedAd(adInfo: SPAdInfo) {
#if canImport(AppLovinSDK)
self.appLovin_needShowRewardedAd = true
if appLovin_rewardedAd?.isReady == true {
self.appLovin_show()
} else {
appLovin_loadRewardedAd(adInfo: adInfo)
}
#endif
}
///广
private func appLovin_show() {
#if canImport(AppLovinSDK)
if appLovin_rewardedAd?.isReady == true, self.appLovin_needShowRewardedAd {
self.appLovin_needShowRewardedAd = false
appLovin_rewardedAd?.show()
}
#endif
}
}
#if canImport(AppLovinSDK)
//MARK: -------------- MARewardedAdDelegate --------------
extension SPRewardedAdManager: MARewardedAdDelegate {
func didLoad(_ ad: MAAd)
{
self.appLovin_isLoadingRewardedAd = false
loadFinishHandler()
appLovin_show()
}
func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError)
{
self.appLovin_isLoadingRewardedAd = false
let nsError = NSError(domain: error.message, code: error.code.rawValue)
loadFailHandler(error: nsError)
}
func didDisplay(_ ad: MAAd) {
self.didShowHandler()
}
func didClick(_ ad: MAAd) {
self.didClickHandler()
}
func didHide(_ ad: MAAd)
{
self.didDismissHandler()
}
func didFail(toDisplay ad: MAAd, withError error: MAError)
{
// Rewarded ad failed to display. AppLovin recommends that you load the next ad.
self.appLovin_isLoadingRewardedAd = false
// appLovin_loadRewardedAd()
let nsError = NSError(domain: error.message, code: error.code.rawValue)
displayFailHandler(error: nsError)
}
func didRewardUser(for ad: MAAd, with reward: MAReward)
{
// Rewarded ad was displayed and user should receive the reward
self.userDidEarnRewardHandler()
}
}
#endif

View File

@ -0,0 +1,321 @@
//
// SPRewardedAdManager.swift
// ThimraTV
//
// Created by on 2025/7/10.
//
import UIKit
@objc protocol SPRewardedAdManagerDelegate: NSObjectProtocol {
///
@objc optional func rewardedAdManager(manager: SPRewardedAdManager, userDidEarnReward adInfo: SPAdInfo)
///广
@objc optional func rewardedAdManager(manager: SPRewardedAdManager, didLoadFail error: Error)
///广
@objc optional func rewardedAdManagerDidLoadFinish(manager: SPRewardedAdManager)
///广
@objc optional func rewardedAdManager(manager: SPRewardedAdManager, didDisplayFail error: Error)
///广
@objc optional func rewardedAdManagerDidShow(manager: SPRewardedAdManager)
///广
@objc optional func rewardedAdManager(manager: SPRewardedAdManager, didDismiss adInfo: SPAdInfo)
}
class SPRewardedAdManager: NSObject {
static let manager = SPRewardedAdManager()
weak var delegate: SPRewardedAdManagerDelegate?
private(set) var isLoadingRewardedAd = false
///
private var retryAttempt = 0.0
///
private var retryCount = 0
///
private let retryMaxCount = 1
///广
private(set) var isEnable = true
private(set) var adInfo: SPAdInfo?
private(set) var isShowLoading = false
private(set) var isShowToast = false
private var adsDate: Date?
///
var statScene: SPStatAdModel.AdScene?
var videoInfo: SPVideoInfoModel?
deinit {
NotificationCenter.default.removeObserver(self)
}
override init() {
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackgroundNotification), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
func isAdAvailable() -> Bool {
guard let adInfo = adInfo else { return false }
if adInfo.platform_key == .google {
return admob_isReady()
} else if adInfo.platform_key == .applovin {
return appLovin_isReady()
}
return false
}
///广
func loadAndShowRewardedAd(isShowLoading: Bool = false, isShowToast: Bool = true) {
guard isEnable else {
let text = "movia_no_ads_tip".localized
if isShowToast {
self.showToast(text: text)
}
self.isShowToast = false
self.isShowLoading = false
let error = NSError(domain: text.localized, code: -1)
loadFailHandler(isStat: false, error: error)
return
}
self.isShowLoading = isShowLoading
self.isShowToast = isShowToast
if self.isShowLoading {
SPHUD.show()
}
guard !isLoadingRewardedAd else { return }
if !isAdAvailable() {
self.isLoadingRewardedAd = true
}
self.requestAdInfo { [weak self] adInfo in
guard let self = self else { return }
if adInfo.platform_key == .google {
self.admob_loadAndShowRewardedAd(adInfo: adInfo)
} else if adInfo.platform_key == .applovin {
self.appLovin_loadAndShowRewardedAd(adInfo: adInfo)
} else {
self.isLoadingRewardedAd = false
self.adInfo = nil
}
}
}
///广
func preloadRewardedAd() {
guard isEnable else { return }
guard !isLoadingRewardedAd else { return }
isShowLoading = false
isShowToast = false
self.isLoadingRewardedAd = true
self.requestAdInfo { [weak self] adInfo in
guard let self = self else { return }
if adInfo.platform_key == .google {
self.admob_loadRewardedAd(adInfo: adInfo)
} else if adInfo.platform_key == .applovin {
self.appLovin_loadRewardedAd(adInfo: adInfo)
} else {
self.isLoadingRewardedAd = false
self.adInfo = nil
}
}
}
///广
private func retryLoadAd() {
guard isEnable else { return }
guard retryCount < retryMaxCount else {
retryCount = 0
retryAttempt = 0
return
}
retryCount += 1
retryAttempt += 1
let delaySec = pow(2.0, min(6.0, retryAttempt))
DispatchQueue.main.asyncAfter(deadline: .now() + delaySec) { [weak self] in
self?.preloadRewardedAd()
}
}
private func clean() {
self.statScene = nil
self.videoInfo = nil
}
}
//MARK: -------------- 广() --------------
extension SPRewardedAdManager {
///
func userDidEarnRewardHandler() {
guard let adInfo = adInfo else { return }
self.delegate?.rewardedAdManager?(manager: self, userDidEarnReward: adInfo)
self.requestStatAd(type: "reward", errorMsg: nil)
}
///广
func loadFailHandler(isStat: Bool = true, error: Error) {
if isShowLoading {
SPHUD.dismiss()
}
if isShowToast {
self.showToast()
}
self.isLoadingRewardedAd = false
self.delegate?.rewardedAdManager?(manager: self, didLoadFail: error)
if isStat {
self.requestStatAd(type: "load_failed", errorMsg: error.localizedDescription)
}
self.clean()
if self.adInfo?.platform_key != .google {//广
self.isLoadingRewardedAd = true
let adInfo = SPAdInfo()
adInfo.platform_key = .google
adInfo.ads_id = SPAdManager.manager.admob_rewardedAdUnitID
self.adInfo = adInfo
self.admob_loadRewardedAd(adInfo: adInfo)
} else {
self.isEnable = false
self.retryLoadAd()
}
}
///广
func loadFinishHandler() {
self.retryAttempt = 0
self.retryCount = 0
self.isLoadingRewardedAd = false
self.delegate?.rewardedAdManagerDidLoadFinish?(manager: self)
}
///广
func displayFailHandler(error: Error) {
if isShowLoading {
SPHUD.dismiss()
}
if isShowToast {
self.showToast()
}
self.delegate?.rewardedAdManager?(manager: self, didDisplayFail: error)
self.requestStatAd(type: "show_failed", errorMsg: error.localizedDescription)
self.clean()
}
///广
func didShowHandler() {
self.adsDate = Date()
if isShowLoading {
SPHUD.dismiss()
}
self.delegate?.rewardedAdManagerDidShow?(manager: self)
self.requestStatAd(type: "start", errorMsg: nil)
}
///广
func didDismissHandler() {
let adInfo = self.adInfo
var seconds = 0
if let adsDate = self.adsDate {
seconds = Int(Date().timeIntervalSince(adsDate))
}
self.requestStatAd(type: "close", seconds: seconds, errorMsg: nil) { [weak self] in
guard let self = self else { return }
if let adInfo = adInfo {
self.delegate?.rewardedAdManager?(manager: self, didDismiss: adInfo)
}
}
self.clean()
self.preloadRewardedAd()
}
///广
func didClickHandler() {
var seconds = 0
if let adsDate = self.adsDate {
seconds = Int(Date().timeIntervalSince(adsDate))
}
self.requestStatAd(type: "click", seconds: seconds, errorMsg: nil)
}
}
extension SPRewardedAdManager {
private func requestStatAd(type: String, seconds: Int = 0, errorMsg: String?, completer: (() -> Void)? = nil) {
guard let adInfo = adInfo else { return }
let model = SPStatAdModel()
model.type = type
model.ads_id = adInfo.ads_id
model.view_seconds = seconds
model.ad_platform_key = adInfo.platform_key
model.error_msg = errorMsg
model.scene = self.statScene
model.short_play_id = self.videoInfo?.short_play_id
model.short_play_video_id = self.videoInfo?.short_play_video_id
SPStatAPI.requestStatAd(model: model, completer: completer)
}
@objc private func didEnterBackgroundNotification() {
if self.statScene == nil { return }
self.requestStatAd(type: "Interrupt", seconds: 0, errorMsg: nil)
}
func showToast(text: String? = "movia_no_ads_tip".localized) {
SPToast.show(text: text)
}
private func requestAdInfo(completer: ((_ adInfo: SPAdInfo) -> Void)?) {
if let adInfo = self.adInfo {
completer?(adInfo)
return
}
SPAdAPI.requestShowAdInfo { [weak self] adInfo in
guard let self = self else { return }
if let adInfo = adInfo {
self.adInfo = adInfo
} else {
self.adInfo = SPAdInfo()
self.adInfo?.platform_key = .google
self.adInfo?.ads_id = SPAdManager.manager.admob_rewardedAdUnitID
}
completer?(self.adInfo!)
}
}
}

View File

@ -0,0 +1,30 @@
//
// SPAdInfo.swift
// ThimraTV
//
// Created by on 2025/7/9.
//
import UIKit
import SmartCodable
enum SPAdPlatformKey: String, SmartCaseDefaultable {
case google = "google"
case applovin = "applovin"
}
class SPAdDataModel: SPModel, SmartCodable {
var ad: SPAdInfo?
}
class SPAdInfo: SPModel, SmartCodable {
var id: String?
var platform_name: String?
var ads_id: String?
var status: String?
var platform_key: SPAdPlatformKey?
}

View File

@ -0,0 +1,98 @@
//
// SPAdManager.swift
// ThimraTV
//
// Created by on 2025/7/9.
//
import UIKit
import GoogleMobileAds
#if canImport(AppLovinSDK)
import AppLovinSDK
#endif
class SPAdManager: NSObject {
static let manager = SPAdManager()
/// AppLovinSDK
private var appLovininitializeCompleter: (() -> Void)?
func start() {
//admob
MobileAds.shared.start()
initialize_applovinSdk(completer: nil)
}
///AppLovinSDK
func initialize_applovinSdk(completer: (() -> Void)? = nil) {
if completer != nil {
self.appLovininitializeCompleter = completer
}
#if canImport(AppLovinSDK)
if !ALSdk.shared().isInitialized {
//
let initConfig = ALSdkInitializationConfiguration(sdkKey: "XW2aulJv9urKD4MIIFT1xcSCuyTHaDZ9qUbDqygnTLS04GkdX7WMQJviGP5vDRWGsk4OJJIyLGRV3mbLqOWx0W") { builder in
builder.mediationProvider = ALMediationProviderMAX
//#if DEBUG
// builder.testDeviceAdvertisingIdentifiers = [JXUUID.idfa()]
//#endif
}
ALSdk.shared().initialize(with: initConfig) { sdkConfig in
// Start loading ads
self.appLovininitializeCompleter?()
}
} else {
self.appLovininitializeCompleter?()
}
#endif
}
}
//MARK: -------------- ID --------------
extension SPAdManager {
///广ID
var admob_rewardedAdUnitID: String {
//#if DEBUG
// return "ca-app-pub-3940256099942544/1712485313"
//#else
//#endif
return "ca-app-pub-5209443898911659/3535954087"
}
///广ID
var admob_appOpenAdUnitID: String {
//#if DEBUG
// return "ca-app-pub-3940256099942544/5575463023"
//#else
//#endif
return "ca-app-pub-5209443898911659/3886651330"
}
///广ID
var admob_bannerAdUnitID: String {
//#if DEBUG
// return "ca-app-pub-3940256099942544/2435281174"
//#else
//#endif
return "ca-app-pub-5209443898911659/8858726666"
}
///广ID
var applovin_appOpenAdUnitID: String {
return ""
}
///广ID
var applovin_bannerAdUnitID: String {
return ""
}
}

View File

@ -0,0 +1,29 @@
//
// SPStatAdModel.swift
// ThimraTV
//
// Created by on 2025/7/10.
//
import UIKit
import SmartCodable
class SPStatAdModel: SPModel, SmartCodable {
enum AdScene: String, SmartCaseDefaultable {
case detail = "detail"
case me = "me"
case reward = "reward"
case splash = "splash"
case banner = "banner"
}
var type: String? //start click error click show_failed load_failed Interrupt(退) close
var ads_id: String?
var view_seconds: Int?
var ad_platform_key: SPAdPlatformKey?
var scene: AdScene? // splash reward banner detail me turntable
var short_play_id: String?
var short_play_video_id: String?
var error_msg: String?
}

View File

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

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,6 +10,11 @@ import SmartCodable
class SPUserInfo: SPModel, SmartCodable, NSSecureCoding {
enum UserLevel: String, SmartCaseDefaultable {
case normal = "normal"
case ad = "ad"
}
var id: String?
var customer_id: String?
@ -28,7 +33,7 @@ class SPUserInfo: SPModel, SmartCodable, NSSecureCoding {
var third_access_platform: String?
var ip_address: String?
var country_code: String?
var user_level: String?
var user_level: UserLevel?
var avator: String?
var sign_in_status: String?
var registered_days: String?
@ -46,23 +51,28 @@ class SPUserInfo: SPModel, SmartCodable, NSSecureCoding {
func encode(with coder: NSCoder) {
// coder.encode(id, forKey: "id")
// coder.encode(phone, forKey: "phone")
// coder.encode(userToken, forKey: "userToken")
// coder.encode(ipAddress, forKey: "ipAddress")
// coder.encode(audioNum, forKey: "audioNum")
// coder.encode(audioSeconds, forKey: "audioSeconds")
coder.encode(id, forKey: "id")
coder.encode(customer_id, forKey: "customer_id")
coder.encode(user_level?.rawValue, forKey: "user_level")
coder.encode(is_vip, forKey: "is_vip")
coder.encode(family_name, forKey: "family_name")
coder.encode(giving_name, forKey: "giving_name")
}
required init?(coder: NSCoder) {
super.init()
// id = coder.decodeObject(of: NSString.self, forKey: "id") as? String
// phone = coder.decodeObject(of: NSString.self, forKey: "phone") as? String
// userToken = coder.decodeObject(of: NSString.self, forKey: "userToken") as? String
// ipAddress = coder.decodeObject(of: NSString.self, forKey: "ipAddress") as? String
// audioNum = coder.decodeObject(of: NSNumber.self, forKey: "audioNum")?.intValue
// audioSeconds = coder.decodeObject(of: NSNumber.self, forKey: "audioSeconds")?.intValue
id = coder.decodeObject(of: NSString.self, forKey: "id") as? String
customer_id = coder.decodeObject(of: NSString.self, forKey: "customer_id") as? String
if let user_level = coder.decodeObject(of: NSString.self, forKey: "user_level") as? String {
self.user_level = UserLevel(rawValue: user_level)
}
is_vip = coder.decodeObject(of: NSNumber.self, forKey: "is_vip")?.boolValue
family_name = coder.decodeObject(of: NSString.self, forKey: "family_name") as? String
giving_name = coder.decodeObject(of: NSString.self, forKey: "giving_name") as? String
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 B

After

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -5,10 +5,12 @@
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

View File

@ -29,11 +29,628 @@
<string>$(PRODUCT_NAME)</string>
<key>FirebaseAppDelegateProxyEnabled</key>
<false/>
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-5209443898911659~1849745003</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>fbapi</string>
<string>fb-messenger-share-api</string>
</array>
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>22mmun2rn5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>238da6jt44.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>24t9a8vw3c.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>24zw6aqk47.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>252b5q8x7y.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>275upjj5gd.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>294l99pt4k.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>2fnua5tdw4.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>2u9pt9hc89.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>32z4fx6l9h.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3l6bd9hu43.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3qcr597p9d.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3qy4746246.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3rd42ekr43.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3sh42y64q3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>424m5254lk.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4468km3ulz.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>44jx6755aq.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>44n7hlldy6.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>47vhws6wlr.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>488r3q3dtq.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4dzt52r2t5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4fzdc2evr5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4mn522wn87.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4pfyvq9l8r.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4w7y6s5ca2.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>523jb4fst2.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>52fl2v3hgk.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>54nzkqm89y.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>578prtvx9j.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>5a6flpkh64.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>5l3tpt7t6e.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>5lm9lj6jb7.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>5tjdwbrq8w.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>6964rsfnh4.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>6g9af3uyq4.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>6p4ks3rnbw.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>6v7lgmsu45.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>6xzpu9s2p8.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>737z793b9f.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>74b6s63p6l.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>79pbpufp6p.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>7fmhfwg9en.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>7rz58n8ntl.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>7ug5zh24hu.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>84993kbrcf.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>89z7zv988g.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>8c4e2ghe7u.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>8m87ys6875.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>8r8llnkz5a.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>8s468mfl3y.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>97r2b46745.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>9b89h5y424.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>9nlqeag3gk.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>9rd848q2bz.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>9t245vhmpl.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>9vvzujtq5s.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>9yg77x724h.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>a2p9lx4jpn.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>a7xqa6mtl2.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>a8cz6cu7e5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>av6w8kgt66.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>b9bk5wbcq9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>bxvub5ada5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>c3frkrj4fj.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>c6k4g5qg8m.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cg4yq2srnc.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cj5566h2ga.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cp8zw746q7.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cs644xg564.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>dbu4b84rxf.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>dkc879ngq3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>dzg6xy7pwj.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>e5fvkxwrpn.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ecpz2srf59.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>eh6m2bh4zr.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ejvt5qm6ak.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>f38h382jlk.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>f73kdq92p3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>f7s53z58qe.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>feyaarzu9v.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>g28c52eehv.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>g2y4y55b64.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>g6gcrrvk4p.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ggvn48r87g.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>glqzh8vgby.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>gta8lk7p23.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>gta9lk7p23.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>hb56zgv37p.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>hdw39hrw9y.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>hs6bdukanm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>k674qkevps.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>kbd757ywx3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>kbmxgpxpgc.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>klf5c3l5u5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>krvm3zuq6h.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>lr83yxwka7.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ludvb6z3bs.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>m297p6643m.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>m5mvw97r93.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>m8dbw4sv7c.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>mj797d8u6f.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>mlmmfzh3r3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>mls7yz5dvl.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>mp6xlyr22a.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>mqn7fxpca7.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>mtkv5xtk9e.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>n38lu8286q.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>n66cz3y3bx.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>n6fk4nfna4.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>n9x2a789qt.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ns5j362hk7.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>nzq8sh4pbs.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>p78axxw29g.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ppxm28t8ap.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>prcb7njmu6.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>pwa73g5rt2.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>pwdxu55a5a.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>qqp299437r.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>qu637u8glc.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>r45fhb6rf7.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>rvh3l7un93.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>rx5hdcabgc.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>s39g8k73mm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>s69wq72ugq.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>su67r6k2v3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>t38b2kh725.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>tl55sbb4fm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>u679fj5vs4.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>uw77j35x4d.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v4nxqhlyqp.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v72qych5uu.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v79kvwwj4g.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v9wttpbfk9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>vcra2ehyfk.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>vhf287vqwu.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>vutu7akeur.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>w9q455wk68.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>wg4vff78zm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>wzmmz9fp6w.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>x44k69ngh6.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>x5l83yy675.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>x8jxxk4ff5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>x8uqf25wch.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>xga6mpmplv.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>xy9t38ct57.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>y45688jllp.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>y5ghdn5j9k.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>yclnxrl5pm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ydx93a7ass.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>zmvfpc5aq8.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>zq492l623r.skadnetwork</string>
</dict>
</array>
<key>UIAppFonts</key>
<array>
<string>Inter-ExtraBold-5.otf</string>

View File

@ -87,6 +87,7 @@
"movia_purchase_VIP" = "Purchase VIP";
"movia_video_lock_tip_01" = "This episode is locked";
"movia_video_lock_tip_02" = "Please unlock the previous episode";
"movia_video_lock_tip_03" = "Unlock watching an ad";
"movia_unlock_now_for" = "Unlock now for";
"movia_Purchase_Single_Episode" = "Purchase Single Episode";
"movia_Retry" = "Retry";
@ -124,6 +125,8 @@
"movia_profile_Bonus" = "Bonus";
"movia_updatenowtitle" = "Discover a new version";
"movia_updatenow" = "Update Now";
"movia_no_short_found" = "No Short Found";
"movia_no_ads_tip" = "Ad is on the way";
"movia_vip_alert_text_01" = "Short Drama VIP Exclusive";

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.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.product = product
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;
}