diff --git a/ThimraTV.xcodeproj/project.pbxproj b/ThimraTV.xcodeproj/project.pbxproj index 57e516b..e71e64a 100644 --- a/ThimraTV.xcodeproj/project.pbxproj +++ b/ThimraTV.xcodeproj/project.pbxproj @@ -280,6 +280,7 @@ 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 */; }; C3D1CE788CA03A1878493356 /* Pods_ThimraTV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B64805795B479324EB764157 /* Pods_ThimraTV.framework */; }; /* End PBXBuildFile section */ @@ -591,6 +592,7 @@ 1BF5130B2E1F4654009750EA /* SPRewardedAdManager+AppLovin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SPRewardedAdManager+AppLovin.swift"; sourceTree = ""; }; 1BF5130D2E1F5D8F009750EA /* SPRewardedAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPRewardedAdManager.swift; sourceTree = ""; }; 1BF513102E1FA138009750EA /* SPStatAdModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPStatAdModel.swift; sourceTree = ""; }; + 1BF513132E1FB8C1009750EA /* SPAppOpenAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAppOpenAdManager.swift; sourceTree = ""; }; 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 = ""; }; 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 = ""; }; @@ -1480,6 +1482,7 @@ 1BDE20112E1E158400C2C2B5 /* AdManager */ = { isa = PBXGroup; children = ( + 1BF513122E1FB897009750EA /* AppOpenAd */, 1BF5130F2E1F5EE4009750EA /* RewardedAd */, 1BDE20122E1E159B00C2C2B5 /* SPAdManager.swift */, 1BDE20182E1E175800C2C2B5 /* SPAdInfo.swift */, @@ -1498,6 +1501,14 @@ path = RewardedAd; sourceTree = ""; }; + 1BF513122E1FB897009750EA /* AppOpenAd */ = { + isa = PBXGroup; + children = ( + 1BF513132E1FB8C1009750EA /* SPAppOpenAdManager.swift */, + ); + path = AppOpenAd; + sourceTree = ""; + }; 1DBC40502DA4EDFC0093FCB0 = { isa = PBXGroup; children = ( @@ -1773,6 +1784,7 @@ 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 */, @@ -2094,7 +2106,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 = ""; @@ -2142,7 +2154,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 = ""; diff --git a/ThimraTV/Base/Controller/SPTabBarController.swift b/ThimraTV/Base/Controller/SPTabBarController.swift index 46d1759..fc9018e 100644 --- a/ThimraTV/Base/Controller/SPTabBarController.swift +++ b/ThimraTV/Base/Controller/SPTabBarController.swift @@ -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")) diff --git a/ThimraTV/Base/Networking/API/SPStatAPI.swift b/ThimraTV/Base/Networking/API/SPStatAPI.swift index 72071b5..b2cae54 100644 --- a/ThimraTV/Base/Networking/API/SPStatAPI.swift +++ b/ThimraTV/Base/Networking/API/SPStatAPI.swift @@ -74,7 +74,7 @@ class SPStatAPI: NSObject { } ///广告统计 - static func requestStatAd(model: SPStatAdModel) { + static func requestStatAd(model: SPStatAdModel, completer: (() -> Void)? = nil) { var param = SPNetworkParameters(path: "/ad/history") param.isToast = false @@ -82,7 +82,7 @@ class SPStatAPI: NSObject { param.parameters = model.toDictionary() SPNetwork.request(parameters: param) { (response: SPNetworkResponse) in - + completer?() } } diff --git a/ThimraTV/Base/WebView/SPWebView.swift b/ThimraTV/Base/WebView/SPWebView.swift index 039d215..fb8635b 100644 --- a/ThimraTV/Base/WebView/SPWebView.swift +++ b/ThimraTV/Base/WebView/SPWebView.swift @@ -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) } diff --git a/ThimraTV/Base/WebView/SPWebViewController+ScriptMessage.swift b/ThimraTV/Base/WebView/SPWebViewController+ScriptMessage.swift index 1983768..685bd5a 100644 --- a/ThimraTV/Base/WebView/SPWebViewController+ScriptMessage.swift +++ b/ThimraTV/Base/WebView/SPWebViewController+ScriptMessage.swift @@ -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,13 @@ extension SPWebViewController { } } - - + + } else if name == WebMessageOpenCheckSignIn { //点击点到 + self.needAutoRefresh = false + let manager = SPRewardedAdManager.manager + manager.statScene = .reward + manager.delegate = self + manager.loadAndShowRewardedAd() } @@ -117,3 +124,22 @@ extension SPWebViewController: TZImagePickerControllerDelegate { } } + +//MARK: -------------- SPRewardedAdManagerDelegate -------------- +extension SPWebViewController: SPRewardedAdManagerDelegate { + + func rewardedAdManager(manager: SPRewardedAdManager, didLoadFail error: any Error) { + self.needAutoRefresh = true + } + + func rewardedAdManager(manager: SPRewardedAdManager, didDisplayFail error: any Error) { + self.needAutoRefresh = true + } + + func rewardedAdManagerDidDismiss(manager: SPRewardedAdManager) { + self.needAutoRefresh = true + + let js = "uploadCheckSignIn()" + self.webView.evaluateJavaScript(js) + } +} diff --git a/ThimraTV/Base/WebView/SPWebViewController.swift b/ThimraTV/Base/WebView/SPWebViewController.swift index 1268df4..579028c 100644 --- a/ThimraTV/Base/WebView/SPWebViewController.swift +++ b/ThimraTV/Base/WebView/SPWebViewController.swift @@ -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() { diff --git a/ThimraTV/Class/Mine/Controller/SPMineViewController.swift b/ThimraTV/Class/Mine/Controller/SPMineViewController.swift index 6752e62..f0517a1 100644 --- a/ThimraTV/Class/Mine/Controller/SPMineViewController.swift +++ b/ThimraTV/Class/Mine/Controller/SPMineViewController.swift @@ -118,6 +118,7 @@ extension SPMineViewController { guard SPLoginManager.manager.userInfo?.user_level == .ad else { return } guard needShowRewardedAd else { return } needShowRewardedAd = false + guard SPRewardedAdManager.manager.isEnable else { return } let manager = SPRewardedAdManager.manager manager.statScene = .me diff --git a/ThimraTV/Class/Rewards/Controller/SPRewardsViewController.swift b/ThimraTV/Class/Rewards/Controller/SPRewardsViewController.swift index ed3b7f5..2880e88 100644 --- a/ThimraTV/Class/Rewards/Controller/SPRewardsViewController.swift +++ b/ThimraTV/Class/Rewards/Controller/SPRewardsViewController.swift @@ -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 } diff --git a/ThimraTV/Libs/AdManager/AppOpenAd/SPAppOpenAdManager.swift b/ThimraTV/Libs/AdManager/AppOpenAd/SPAppOpenAdManager.swift new file mode 100644 index 0000000..d2e0823 --- /dev/null +++ b/ThimraTV/Libs/AdManager/AppOpenAd/SPAppOpenAdManager.swift @@ -0,0 +1,83 @@ +// +// SPAppOpenAdManager.swift +// ThimraTV +// +// Created by 长沙佳儿 on 2025/7/10. +// + +import UIKit +import GoogleMobileAds + +class SPAppOpenAdManager: NSObject { + + var appOpenAd: AppOpenAd? + var isLoadingAd = false + var isShowingAd = false + + static let shared = SPAppOpenAdManager() + + private func loadAd() async { + // Do not load ad if there is an unused ad or one is already loading. + if isLoadingAd || isAdAvailable() { + return + } + isLoadingAd = true + + do { + appOpenAd = try await AppOpenAd.load(with: "ca-app-pub-3940256099942544/5575463023", request: Request()) + + appOpenAd?.fullScreenContentDelegate = self + } catch { + print("App open ad failed to load with error: \(error.localizedDescription)") + } + isLoadingAd = false + } + + func showAdIfAvailable() { + // If the app open ad is already showing, do not show the ad again. + guard !isShowingAd else { return } + + // If the app open ad is not available yet but is supposed to show, load + // a new ad. + if !isAdAvailable() { + Task { + await loadAd() + } + return + } + + if let ad = appOpenAd { + isShowingAd = true + ad.present(from: nil) + } + } + + private func isAdAvailable() -> Bool { + // Check if ad exists and can be shown. + return appOpenAd != nil + } +} + +extension SPAppOpenAdManager: FullScreenContentDelegate { + func adWillPresentFullScreenContent(_ ad: FullScreenPresentingAd) { + print("App open ad will be presented.") + } + + func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) { + appOpenAd = nil + isShowingAd = false + // Reload an ad. + Task { + await loadAd() + } + } + + func ad(_ ad: FullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) { + appOpenAd = nil + isShowingAd = false + // Reload an ad. + Task { + await loadAd() + } + } +} diff --git a/ThimraTV/Libs/AdManager/RewardedAd/SPRewardedAdManager.swift b/ThimraTV/Libs/AdManager/RewardedAd/SPRewardedAdManager.swift index 9b0644f..740cb5f 100644 --- a/ThimraTV/Libs/AdManager/RewardedAd/SPRewardedAdManager.swift +++ b/ThimraTV/Libs/AdManager/RewardedAd/SPRewardedAdManager.swift @@ -36,6 +36,8 @@ class SPRewardedAdManager: NSObject { private var retryCount = 0 ///最大重试次数 private let retryMaxCount = 1 + ///广告激活状态 + private(set) var isEnable = true private(set) var adInfo: SPAdInfo? @@ -47,6 +49,7 @@ class SPRewardedAdManager: NSObject { var statScene: SPStatAdModel.AdScene? var videoInfo: SPVideoInfoModel? + deinit { NotificationCenter.default.removeObserver(self) } @@ -57,23 +60,41 @@ class SPRewardedAdManager: NSObject { } ///加载并展示激励广告 - func loadAndShowRewardedAd() { - isShowLoading = true - isShowToast = true + func loadAndShowRewardedAd(isShowLoading: Bool = true, isShowToast: Bool = true) { + guard isEnable else { + let text = "movia_no_ads_tip".localized + if isShowToast { + SPToast.show(text: text) + } + self.isShowToast = false + self.isShowLoading = false + let error = NSError(domain: text.localized, code: -1) + loadFailHandler(isStat: false, error: error) + return + } - SPHUD.show() + self.isShowLoading = isShowLoading + self.isShowToast = isShowToast - if let adInfo = adInfo {//已有广告,并且加载完成 + if self.isShowLoading { + SPHUD.show() + } + + if let adInfo = adInfo {//已有广告 if adInfo.platform_key == .google { self.admob_loadAndShowRewardedAd(adInfo: adInfo) } } else { + guard !isLoadingRewardedAd else { return } + self.isLoadingRewardedAd = true + SPAdAPI.requestShowAdInfo { [weak self] adInfo in guard let self = self else { return } guard let adInfo = adInfo else { - SPHUD.dismiss() - SPToast.show(text: "movia_no_ads_tip".localized) + let text = "movia_no_ads_tip".localized + let error = NSError(domain: text.localized, code: -1) + loadFailHandler(isStat: false, error: error) return } self.adInfo = adInfo @@ -89,6 +110,7 @@ class SPRewardedAdManager: NSObject { ///预加载一个广告 func preloadRewardedAd() { + guard isEnable else { return } guard !isLoadingRewardedAd else { return } isShowLoading = false isShowToast = false @@ -112,6 +134,8 @@ class SPRewardedAdManager: NSObject { ///重试加载广告 private func retryLoadAd() { + guard isEnable else { return } + guard retryCount < retryMaxCount else { retryCount = 0 retryAttempt = 0 @@ -141,7 +165,7 @@ extension SPRewardedAdManager { } ///广告加载失败 - func loadFailHandler(error: Error) { + func loadFailHandler(isStat: Bool = true, error: Error) { if isShowLoading { SPHUD.dismiss() @@ -151,10 +175,13 @@ extension SPRewardedAdManager { } self.isLoadingRewardedAd = false self.delegate?.rewardedAdManager?(manager: self, didLoadFail: error) - self.requestStatAd(type: "load_failed", errorMsg: error.localizedDescription) + if isStat { + self.requestStatAd(type: "load_failed", errorMsg: error.localizedDescription) + } self.statScene = nil self.videoInfo = nil + self.isEnable = false self.retryLoadAd() } @@ -195,13 +222,16 @@ extension SPRewardedAdManager { } ///广告被关闭 func didDismissHandler() { - self.delegate?.rewardedAdManagerDidDismiss?(manager: self) var seconds = 0 if let adsDate = self.adsDate { seconds = Int(Date().timeIntervalSince(adsDate)) } - self.requestStatAd(type: "close", seconds: seconds, errorMsg: nil) + + self.requestStatAd(type: "close", seconds: seconds, errorMsg: nil) { [weak self] in + guard let self = self else { return } + self.delegate?.rewardedAdManagerDidDismiss?(manager: self) + } self.statScene = nil self.videoInfo = nil @@ -224,7 +254,7 @@ extension SPRewardedAdManager { //MARK: -------------- 统计 -------------- extension SPRewardedAdManager { - private func requestStatAd(type: String, seconds: Int = 0, errorMsg: String?) { + private func requestStatAd(type: String, seconds: Int = 0, errorMsg: String?, completer: (() -> Void)? = nil) { guard let adInfo = adInfo else { return } let model = SPStatAdModel() @@ -237,7 +267,7 @@ extension SPRewardedAdManager { model.short_play_id = self.videoInfo?.short_play_id model.short_play_video_id = self.videoInfo?.short_play_video_id - SPStatAPI.requestStatAd(model: model) + SPStatAPI.requestStatAd(model: model, completer: completer) } diff --git a/ThimraTV/Libs/AdManager/SPStatAdModel.swift b/ThimraTV/Libs/AdManager/SPStatAdModel.swift index a51fee1..ca075b8 100644 --- a/ThimraTV/Libs/AdManager/SPStatAdModel.swift +++ b/ThimraTV/Libs/AdManager/SPStatAdModel.swift @@ -13,6 +13,7 @@ class SPStatAdModel: SPModel, SmartCodable { enum AdScene: String, SmartCaseDefaultable { case detail = "detail" case me = "me" + case reward = "reward" } var type: String? //start click error click show_failed load_failed Interrupt(退到后台) close diff --git a/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03.imageset/Frame@2x.png b/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03.imageset/Frame@2x.png index 8d492fa..9814033 100644 Binary files a/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03.imageset/Frame@2x.png and b/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03.imageset/Frame@2x.png differ diff --git a/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03.imageset/Frame@3x.png b/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03.imageset/Frame@3x.png index 78e9ca5..52ec7b0 100644 Binary files a/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03.imageset/Frame@3x.png and b/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03.imageset/Frame@3x.png differ diff --git a/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03_selected.imageset/Contents.json b/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03_selected.imageset/Contents.json index d77ddd3..6aa90f6 100644 --- a/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03_selected.imageset/Contents.json +++ b/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03_selected.imageset/Contents.json @@ -5,10 +5,12 @@ "scale" : "1x" }, { + "filename" : "Frame@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "Frame@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03_selected.imageset/Frame@2x.png b/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03_selected.imageset/Frame@2x.png new file mode 100644 index 0000000..40d6eea Binary files /dev/null and b/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03_selected.imageset/Frame@2x.png differ diff --git a/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03_selected.imageset/Frame@3x.png b/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03_selected.imageset/Frame@3x.png new file mode 100644 index 0000000..4072220 Binary files /dev/null and b/ThimraTV/Source/Assets.xcassets/TabBar/tabbar_icon_03_selected.imageset/Frame@3x.png differ