This commit is contained in:
HiBit 2025-11-28 15:03:10 +08:00
parent 86b603c64f
commit b21751e738
49 changed files with 1723 additions and 10 deletions

View File

@ -32,4 +32,5 @@ target 'SynthReel' do
pod 'collection-view-layouts/TagsLayout'
pod 'HWPanModal'
pod 'LYEmptyView'
pod 'ZLPhotoBrowser'
end

View File

@ -125,6 +125,21 @@
3754ACBF2ED5B839009EBCAD /* SRNavgationTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACBE2ED5B839009EBCAD /* SRNavgationTitleView.swift */; };
3754ACC12ED69105009EBCAD /* SRTopChartsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACC02ED69105009EBCAD /* SRTopChartsHeaderView.swift */; };
3754ACC42ED6EDA6009EBCAD /* SREmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACC32ED6EDA6009EBCAD /* SREmpty.swift */; };
3754ACD12ED81F55009EBCAD /* SRBaseAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACD02ED81F55009EBCAD /* SRBaseAlert.swift */; };
3754ACD32ED82113009EBCAD /* SRGradientbutton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACD22ED82113009EBCAD /* SRGradientbutton.swift */; };
3754ACD52ED82722009EBCAD /* SRDetailRecommendview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACD42ED82722009EBCAD /* SRDetailRecommendview.swift */; };
3754ACD72ED82774009EBCAD /* SRDetailRecommendCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACD62ED82774009EBCAD /* SRDetailRecommendCell.swift */; };
3754ACDA2ED8374D009EBCAD /* SRPagerViewTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACD92ED8374D009EBCAD /* SRPagerViewTransformer.swift */; };
3754ACDC2ED83F80009EBCAD /* SRHomeHistoryBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACDB2ED83F80009EBCAD /* SRHomeHistoryBottomView.swift */; };
3754ACE12ED93C4D009EBCAD /* SRCoinPackController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACE02ED93C4D009EBCAD /* SRCoinPackController.swift */; };
3754ACE42ED93E3B009EBCAD /* SRBaseWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACE32ED93E3B009EBCAD /* SRBaseWebViewController.swift */; };
3754ACE62ED93E7C009EBCAD /* SRWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACE52ED93E7C009EBCAD /* SRWebView.swift */; };
3754ACE82ED93ED9009EBCAD /* SRWebMessageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACE72ED93ED9009EBCAD /* SRWebMessageModel.swift */; };
3754ACEA2ED94065009EBCAD /* SRBaseWebViewController + Script.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACE92ED94065009EBCAD /* SRBaseWebViewController + Script.swift */; };
3754ACEC2ED943AB009EBCAD /* SRAppWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACEB2ED943AB009EBCAD /* SRAppWebViewController.swift */; };
3754ACEE2ED945A1009EBCAD /* Dictionary+SRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACED2ED945A1009EBCAD /* Dictionary+SRAdd.swift */; };
3754ACF02ED94A4C009EBCAD /* SRFeedBackController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACEF2ED94A4C009EBCAD /* SRFeedBackController.swift */; };
3754ACF22ED975B9009EBCAD /* SRRewardController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACF12ED975B9009EBCAD /* SRRewardController.swift */; };
3779D0612ECF1CB8006B1698 /* SRShortHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3779D0602ECF1CB8006B1698 /* SRShortHeaderView.swift */; };
47BB39E2DD30787FA591F8EB /* Pods_SynthReel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9255BF4D4B1CFDDB5CFFB43 /* Pods_SynthReel.framework */; };
/* End PBXBuildFile section */
@ -249,6 +264,21 @@
3754ACBE2ED5B839009EBCAD /* SRNavgationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRNavgationTitleView.swift; sourceTree = "<group>"; };
3754ACC02ED69105009EBCAD /* SRTopChartsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRTopChartsHeaderView.swift; sourceTree = "<group>"; };
3754ACC32ED6EDA6009EBCAD /* SREmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SREmpty.swift; sourceTree = "<group>"; };
3754ACD02ED81F55009EBCAD /* SRBaseAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRBaseAlert.swift; sourceTree = "<group>"; };
3754ACD22ED82113009EBCAD /* SRGradientbutton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRGradientbutton.swift; sourceTree = "<group>"; };
3754ACD42ED82722009EBCAD /* SRDetailRecommendview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRDetailRecommendview.swift; sourceTree = "<group>"; };
3754ACD62ED82774009EBCAD /* SRDetailRecommendCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRDetailRecommendCell.swift; sourceTree = "<group>"; };
3754ACD92ED8374D009EBCAD /* SRPagerViewTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRPagerViewTransformer.swift; sourceTree = "<group>"; };
3754ACDB2ED83F80009EBCAD /* SRHomeHistoryBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRHomeHistoryBottomView.swift; sourceTree = "<group>"; };
3754ACE02ED93C4D009EBCAD /* SRCoinPackController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCoinPackController.swift; sourceTree = "<group>"; };
3754ACE32ED93E3B009EBCAD /* SRBaseWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRBaseWebViewController.swift; sourceTree = "<group>"; };
3754ACE52ED93E7C009EBCAD /* SRWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRWebView.swift; sourceTree = "<group>"; };
3754ACE72ED93ED9009EBCAD /* SRWebMessageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRWebMessageModel.swift; sourceTree = "<group>"; };
3754ACE92ED94065009EBCAD /* SRBaseWebViewController + Script.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SRBaseWebViewController + Script.swift"; sourceTree = "<group>"; };
3754ACEB2ED943AB009EBCAD /* SRAppWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRAppWebViewController.swift; sourceTree = "<group>"; };
3754ACED2ED945A1009EBCAD /* Dictionary+SRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+SRAdd.swift"; sourceTree = "<group>"; };
3754ACEF2ED94A4C009EBCAD /* SRFeedBackController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRFeedBackController.swift; sourceTree = "<group>"; };
3754ACF12ED975B9009EBCAD /* SRRewardController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRRewardController.swift; sourceTree = "<group>"; };
3779D0602ECF1CB8006B1698 /* SRShortHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRShortHeaderView.swift; sourceTree = "<group>"; };
59DC746604B26E9FF802D317 /* Pods-SynthReel.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SynthReel.debug.xcconfig"; path = "Target Support Files/Pods-SynthReel/Pods-SynthReel.debug.xcconfig"; sourceTree = "<group>"; };
AA88214030574193B51DE563 /* Pods-SynthReel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SynthReel.release.xcconfig"; path = "Target Support Files/Pods-SynthReel/Pods-SynthReel.release.xcconfig"; sourceTree = "<group>"; };
@ -354,6 +384,7 @@
03B1A8F82EC813BC006C353F /* SRScrollView.swift */,
03B1A8FE2EC81C92006C353F /* SRImageView.swift */,
03B1A9062EC86656006C353F /* SRGradientView.swift */,
3754ACD22ED82113009EBCAD /* SRGradientbutton.swift */,
03B1A93D2ECC4568006C353F /* SRTableView.swift */,
03B1A93F2ECC45BA006C353F /* SRTableViewCell.swift */,
03B1A94B2ECC7A2D006C353F /* SRPanModalContentView.swift */,
@ -394,6 +425,7 @@
370D2F2A2ED597F700571E77 /* SRTopChartsCell.swift */,
370D2F2C2ED5AA9700571E77 /* SRViralHitCell.swift */,
3754ACC02ED69105009EBCAD /* SRTopChartsHeaderView.swift */,
3754ACDB2ED83F80009EBCAD /* SRHomeHistoryBottomView.swift */,
);
path = V;
sourceTree = "<group>";
@ -465,6 +497,8 @@
03B1A9472ECC6669006C353F /* SRProgressView.swift */,
03B1A9492ECC79AB006C353F /* SREpSelectorView.swift */,
03B1A94D2ECD604B006C353F /* SREpSelectorCell.swift */,
3754ACD42ED82722009EBCAD /* SRDetailRecommendview.swift */,
3754ACD62ED82774009EBCAD /* SRDetailRecommendCell.swift */,
);
path = V;
sourceTree = "<group>";
@ -525,6 +559,7 @@
03E9A7D22EC47204000D1067 /* Base */ = {
isa = PBXGroup;
children = (
3754ACE22ED93E04009EBCAD /* webview */,
03B1A8412EC5CB4F006C353F /* ViewController */,
03B1A8D62EC6D03A006C353F /* View */,
03E9A7F02EC4A8DD000D1067 /* Extension */,
@ -538,6 +573,7 @@
03E9A7D32EC4720F000D1067 /* Class */ = {
isa = PBXGroup;
children = (
3754ACDD2ED93C14009EBCAD /* Coinpack */,
370D2F0D2ED4532100571E77 /* User */,
03980F542ECEEC990006E317 /* MyShort */,
03B1A84E2EC5DB13006C353F /* Home */,
@ -549,6 +585,8 @@
03E9A7D42EC4764A000D1067 /* Libs */ = {
isa = PBXGroup;
children = (
3754ACD82ED83724009EBCAD /* FSPagerView */,
3754ACCF2ED81F2F009EBCAD /* Alert */,
3754ACC22ED6ED92009EBCAD /* Empty */,
03B1A83C2EC5C90F006C353F /* Tool */,
03B1A8372EC5C8BF006C353F /* SRHud */,
@ -611,6 +649,7 @@
03B1A91A2ECAFFCB006C353F /* UIView+SRAdd.swift */,
03B1A91C2ECB241B006C353F /* UIScrollView+SRAdd.swift */,
3754ACBC2ED5B6B6009EBCAD /* UINavigationBar+SRAdd.swift */,
3754ACED2ED945A1009EBCAD /* Dictionary+SRAdd.swift */,
);
path = Extension;
sourceTree = "<group>";
@ -650,6 +689,8 @@
370D2F1D2ED54C7F00571E77 /* SRHelpCenterController.swift */,
370D2F1F2ED54C8F00571E77 /* SRAboutUsController.swift */,
370D2F212ED54CA400571E77 /* SRPrivacyController.swift */,
3754ACEF2ED94A4C009EBCAD /* SRFeedBackController.swift */,
3754ACF12ED975B9009EBCAD /* SRRewardController.swift */,
);
path = VC;
sourceTree = "<group>";
@ -682,6 +723,58 @@
path = Empty;
sourceTree = "<group>";
};
3754ACCF2ED81F2F009EBCAD /* Alert */ = {
isa = PBXGroup;
children = (
3754ACD02ED81F55009EBCAD /* SRBaseAlert.swift */,
);
path = Alert;
sourceTree = "<group>";
};
3754ACD82ED83724009EBCAD /* FSPagerView */ = {
isa = PBXGroup;
children = (
3754ACD92ED8374D009EBCAD /* SRPagerViewTransformer.swift */,
);
path = FSPagerView;
sourceTree = "<group>";
};
3754ACDD2ED93C14009EBCAD /* Coinpack */ = {
isa = PBXGroup;
children = (
3754ACDF2ED93C36009EBCAD /* view */,
3754ACDE2ED93C2A009EBCAD /* VC */,
);
path = Coinpack;
sourceTree = "<group>";
};
3754ACDE2ED93C2A009EBCAD /* VC */ = {
isa = PBXGroup;
children = (
3754ACE02ED93C4D009EBCAD /* SRCoinPackController.swift */,
);
path = VC;
sourceTree = "<group>";
};
3754ACDF2ED93C36009EBCAD /* view */ = {
isa = PBXGroup;
children = (
);
path = view;
sourceTree = "<group>";
};
3754ACE22ED93E04009EBCAD /* webview */ = {
isa = PBXGroup;
children = (
3754ACEB2ED943AB009EBCAD /* SRAppWebViewController.swift */,
3754ACE32ED93E3B009EBCAD /* SRBaseWebViewController.swift */,
3754ACE92ED94065009EBCAD /* SRBaseWebViewController + Script.swift */,
3754ACE52ED93E7C009EBCAD /* SRWebView.swift */,
3754ACE72ED93ED9009EBCAD /* SRWebMessageModel.swift */,
);
path = webview;
sourceTree = "<group>";
};
3779D05F2ECF1C8D006B1698 /* V */ = {
isa = PBXGroup;
children = (
@ -836,8 +929,10 @@
03B1A9402ECC45BA006C353F /* SRTableViewCell.swift in Sources */,
03E9A7EA2EC4995D000D1067 /* SRKeychain.swift in Sources */,
03E9A7D92EC47B90000D1067 /* SRNetworkReachableManager.swift in Sources */,
3754ACD72ED82774009EBCAD /* SRDetailRecommendCell.swift in Sources */,
370D2F2D2ED5AA9700571E77 /* SRViralHitCell.swift in Sources */,
03B1A8532EC5E12E006C353F /* UIScreen+SRAdd.swift in Sources */,
3754ACF02ED94A4C009EBCAD /* SRFeedBackController.swift in Sources */,
370D2F182ED45B3000571E77 /* SRUserSettingModel.swift in Sources */,
03B1A8FB2EC818BE006C353F /* UIStackView+SRAdd.swift in Sources */,
03B1A8E12EC6D6D3006C353F /* SRHomeMenuDataSource.swift in Sources */,
@ -854,12 +949,17 @@
03E9A7DB2EC485BE000D1067 /* SRNetwork.swift in Sources */,
370D2F152ED457F000571E77 /* SRUserTopCell.swift in Sources */,
3754ACBD2ED5B6B6009EBCAD /* UINavigationBar+SRAdd.swift in Sources */,
3754ACE82ED93ED9009EBCAD /* SRWebMessageModel.swift in Sources */,
03E9A7F42EC4A94D000D1067 /* SRAccountToken.swift in Sources */,
3754ACD12ED81F55009EBCAD /* SRBaseAlert.swift in Sources */,
370D2F222ED54CA400571E77 /* SRPrivacyController.swift in Sources */,
370D2EF72ED000C400571E77 /* SRFavoritesViewController.swift in Sources */,
370D2F072ED3FEFA00571E77 /* SRFavoritesCell.swift in Sources */,
3754ACE42ED93E3B009EBCAD /* SRBaseWebViewController.swift in Sources */,
03B1A8E72EC7175D006C353F /* SRCategoryModel.swift in Sources */,
370D2F2F2ED5AB2500571E77 /* SRViralHitController.swift in Sources */,
3754ACDA2ED8374D009EBCAD /* SRPagerViewTransformer.swift in Sources */,
3754ACF22ED975B9009EBCAD /* SRRewardController.swift in Sources */,
03B1A94A2ECC79AB006C353F /* SREpSelectorView.swift in Sources */,
03B1A94E2ECD604B006C353F /* SREpSelectorCell.swift in Sources */,
03B1A9262ECBFF31006C353F /* SRShortPlayerViewModel.swift in Sources */,
@ -869,6 +969,8 @@
03B1A9482ECC6669006C353F /* SRProgressView.swift in Sources */,
370D2F1A2ED45CCA00571E77 /* SRUserSettingCell.swift in Sources */,
03B1A91D2ECB2424006C353F /* UIScrollView+SRAdd.swift in Sources */,
3754ACE62ED93E7C009EBCAD /* SRWebView.swift in Sources */,
3754ACEE2ED945A1009EBCAD /* Dictionary+SRAdd.swift in Sources */,
03E9A7C92EC47177000D1067 /* AppDelegate.swift in Sources */,
03E9A7FA2EC56D03000D1067 /* String+SRAdd.swift in Sources */,
03B1A9192ECAF2E6006C353F /* SRHomePremiereNowView.swift in Sources */,
@ -889,11 +991,13 @@
03B1A9152ECAEE63006C353F /* SRHomeViralHitsCell.swift in Sources */,
03B1A8472EC5CBCF006C353F /* SRViewController.swift in Sources */,
370D2F052ED3FEE700571E77 /* SRHistoryCell.swift in Sources */,
3754ACEA2ED94065009EBCAD /* SRBaseWebViewController + Script.swift in Sources */,
03B1A9032EC8555B006C353F /* SRHomeYouLikeView.swift in Sources */,
03B1A9362ECC1D1D006C353F /* SRSearchRecordView.swift in Sources */,
03B1A8D82EC6D051006C353F /* SRCollectionView.swift in Sources */,
03B1A90D2ECAC51A006C353F /* NSNumber+SRAdd.swift in Sources */,
03E9A7DD2EC485E1000D1067 /* SRNetworkModel.swift in Sources */,
3754ACEC2ED943AB009EBCAD /* SRAppWebViewController.swift in Sources */,
03B1A9442ECC4ED9006C353F /* SRSearchResultView.swift in Sources */,
3754ACBF2ED5B839009EBCAD /* SRNavgationTitleView.swift in Sources */,
03B1A8FF2EC81C92006C353F /* SRImageView.swift in Sources */,
@ -912,6 +1016,7 @@
03B1A8E92EC721CD006C353F /* SRLabel.swift in Sources */,
03B1A9282ECC05B1006C353F /* SRShortDetailModel.swift in Sources */,
03980F592ECEED190006E317 /* SRMyShortViewController.swift in Sources */,
3754ACD32ED82113009EBCAD /* SRGradientbutton.swift in Sources */,
03B1A92A2ECC0738006C353F /* SRShortApi.swift in Sources */,
03B1A90B2ECAB2EA006C353F /* SRHomeTopChartsContentView.swift in Sources */,
370D2F0C2ED44ACB00571E77 /* SRListMenuDataSource.swift in Sources */,
@ -929,6 +1034,7 @@
03B1A9012EC852B2006C353F /* SRHomeModuleView.swift in Sources */,
03E9A7EF2EC4A8AF000D1067 /* SRAccountManager.swift in Sources */,
03B1A8F52EC81277006C353F /* SRHomeBannerView.swift in Sources */,
3754ACDC2ED83F80009EBCAD /* SRHomeHistoryBottomView.swift in Sources */,
03B1A9132ECAED04006C353F /* SRHomeViralHitsView.swift in Sources */,
03B1A8DC2EC6D0EB006C353F /* SRHomeChildCell.swift in Sources */,
03B1A8552EC5E434006C353F /* UIFont+SRAdd.swift in Sources */,
@ -946,6 +1052,8 @@
370D2F1C2ED4770800571E77 /* SRUserInfoModel.swift in Sources */,
03B1A93C2ECC406E006C353F /* SRHotSearchView.swift in Sources */,
03B1A8E32EC6F577006C353F /* SRHomeMenuCell.swift in Sources */,
3754ACE12ED93C4D009EBCAD /* SRCoinPackController.swift in Sources */,
3754ACD52ED82722009EBCAD /* SRDetailRecommendview.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -118,5 +118,4 @@ struct SRHomeApi {
}
}
}
}

View File

@ -29,6 +29,20 @@ struct SRShortApi {
}
}
static func requestRecommand() async -> ([SRShortModel]?, Int?, String?) {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/getDetailsRecommand")
param.method = .get
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRNetwork.List<SRShortModel>>) in
if response.isSuccess {
continuation.resume(returning:(response.data?.list, response.code, response.msg))
} else {
continuation.resume(returning:(nil, response.code, response.msg))
}
}
}
}
static func requestShortCollect(shortId: String, videoId: String?, isCollect: Bool) async -> Bool {
await withCheckedContinuation { continuation in
var path = ""

View File

@ -0,0 +1,24 @@
//
// Dictionary+SRAdd.swift
// SynthReel
//
// Created by CSGY on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
extension Dictionary {
func toJsonString() -> String? {
do {
let data = try JSONSerialization.data(withJSONObject: self)
let jsonStr = String(data: data, encoding: .utf8)
return jsonStr
} catch {
}
return nil
}
}

View File

@ -8,6 +8,7 @@
import UIKit
import YYCategories
import SmartCodable
extension String {
@ -24,3 +25,13 @@ extension String {
}
}
extension String: SmartCodable {
static func timeZone() -> String {
let timeZone = NSTimeZone.local as NSTimeZone
let timeZoneSecondsFromGMT = timeZone.secondsFromGMT / 3600
return String(format: "GMT+0%d:00", timeZoneSecondsFromGMT)
}
}

View File

@ -12,6 +12,18 @@ let SRWebBaseURL = "https://www.synthreeltv.com"
let SRCampaignWebURL = "https://campaign.synthreeltv.com"
///
let kSRFeedBackHomeWebUrl = SRCampaignWebURL + "/pages/leave/index"
///
let kSRFeedBackListWebUrl = SRCampaignWebURL + "/pages/leave/list"
///
let kSRFeedBackDetailWebUrl = SRCampaignWebURL + "/pages/leave/detail"
///
let kSRLogoutWebUrl = SRCampaignWebURL + "/pages/setting/logout"
let kSRRewardWebUrl = SRCampaignWebURL + "/pages/reward/theme4"
/*
SynthReel
admin-api https://admin-api-synthreeltv.synthreeltv.com

View File

@ -0,0 +1,45 @@
//
// SRGradientbutton.swift
// SynthReel
//
// Created by CSGY on 2025/11/27.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRGradientbutton: UIButton {
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
var fa_gradientLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
var fa_colors: [CGColor]? {
didSet {
fa_gradientLayer.colors = fa_colors
}
}
var fa_startPoint: CGPoint = .zero {
didSet {
fa_gradientLayer.startPoint = fa_startPoint
}
}
var fa_endPoint: CGPoint = .zero {
didSet {
fa_gradientLayer.endPoint = fa_endPoint
}
}
var fa_locations: [NSNumber] = [] {
didSet {
fa_gradientLayer.locations = fa_locations
}
}
}

View File

@ -0,0 +1,83 @@
//
// SRAppWebViewController.swift
// SynthReel
//
// Created by CSGY on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRAppWebViewController: SRBaseWebViewController {
var id: String?
private var receiveDataCount = 0
var theme: String? = "theme_16"
override func viewDidLoad() {
super.viewDidLoad()
self.autoTitle = false
if webUrl == kSRFeedBackListWebUrl {
self.title = "synthreel_feedback_history".localized
} else if webUrl == kSRFeedBackHomeWebUrl {
self.title = "synthreel_feedback".localized
} else if webUrl == kSRFeedBackDetailWebUrl {
self.title = "synthreel_feedback_detail".localized
} else if webUrl == kSRLogoutWebUrl {
self.title = "synthreel_account_deletion".localized
}else if webUrl == kSRRewardWebUrl {
self.title = "Rewards".localized
}
}
override func fa_webViewDidFinishLoad(_ webView: SRWebView) {
super.fa_webViewDidFinishLoad(webView)
receiveDataCount = 0
receiveDataFromNative()
}
}
extension SRAppWebViewController {
func receiveDataFromNative() {
receiveDataCount += 1
if receiveDataCount > 10 { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let self = self else { return }
var dic = [
"token" : SRAccountManager.manager.token?.token ?? "",
"time_zone" : String.timeZone(),
// "lang" : FALanguageManager.manager.currentLanguageKey,
"lang" : "en",
"type" : "ios",
"device-id" : SRDeviceId.shared.id
]
if let theme = theme {
dic["theme"] = theme
}
if let id = id {
dic["id"] = id
}
if let json = dic.toJsonString() {
let js = "receiveDataFromNative(\(json))"
self.webView.evaluateJavaScript(js) { [weak self] _, error in
guard let self = self else { return }
if error != nil {
self.receiveDataFromNative()
}
}
}
}
}
}

View File

@ -0,0 +1,112 @@
//
// SRBaseWebViewController + Script.swift
// SynthReel
//
// Created by CSGY on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import WebKit
import ZLPhotoBrowser
import SmartCodable
///APP
let kSRWebMessageAPP = "js2app"
///
let kSRWebMessageOpenFeedbackList = "openFeedbackList"
///
let kSRWebMessageOpenFeedbackDetail = "openFeedbackDetail"
///
let kSRWebMessageOpenPhotoPicker = "openPhotoPicker"
///
let kSRWebMessageAccountDeletionFinish = "accountLogout"
extension SRBaseWebViewController {
func fa_webViewUserContentController(didReceive message: WKScriptMessage) {
let name = message.name
let body = message.body
switch name {
case kSRWebMessageOpenFeedbackList:
let vc = SRAppWebViewController()
vc.webUrl = kSRFeedBackListWebUrl
self.navigationController?.pushViewController(vc, animated: true)
case kSRWebMessageOpenFeedbackDetail:
guard let body = body as? [String : Any] else { return }
guard let id = body["id"] as? Int else { return }
let vc = SRAppWebViewController()
vc.id = "\(id)"
vc.webUrl = kSRFeedBackDetailWebUrl
self.navigationController?.pushViewController(vc, animated: true)
case kSRWebMessageOpenPhotoPicker:
openPhotoPicker()
case kSRWebMessageAPP:
guard let body = message.body as? [String : Any] else { return }
guard let model = SRWebMessageModel.deserialize(from: body) else { return }
let type = model.type
let data = model.data
if type == "login" {
// let view = FALoginView()
// view.present(in: nil)
} else if type == "open_notify" {
// openNotify()
} else if type == "watch_video" {
let vc = SRDetailPlayerViewController()
vc.shortId = data?.short_play_id
// vc.activityId = data?.activity_id
self.navigationController?.pushViewController(vc, animated: true)
} else {
guard let urlStr = data?.link else { return }
guard let url = URL(string: urlStr) else { return }
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
}
case kSRWebMessageAccountDeletionFinish:
self.navigationController?.popToRootViewController(animated: true)
// NotificationCenter.default.post(name: FALogin.loginStatusChangeNotification, object: nil)
default:
break
}
}
///
private func openPhotoPicker() {
ZLPhotoConfiguration.default().allowSelectOriginal = false
ZLPhotoConfiguration.default().maxSelectCount = 1
ZLPhotoConfiguration.default().allowEditImage = false
ZLPhotoConfiguration.default().allowSelectVideo = false
ZLPhotoConfiguration.default().allowSelectGif = false
ZLPhotoConfiguration.default().allowTakePhotoInLibrary = false
let picker = ZLPhotoPicker()
picker.selectImageBlock = { [weak self] (results, _) in
guard let self = self else { return }
guard let image = results.first?.image else { return }
guard let imageData = image.jpegData(compressionQuality: 0.8) else { return }
let imageDataStr = imageData.base64EncodedString(options: .endLineWithCarriageReturn)
let js = "uploadConvertImage('\(imageDataStr)')"
self.webView.evaluateJavaScript(js)
}
picker.showPhotoLibrary(sender: self)
}
}

View File

@ -0,0 +1,114 @@
//
// SRBaseWebViewController.swift
// SynthReel
//
// Created by CSGY on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import WebKit
class SRBaseWebViewController: SRViewController {
var webUrl: String?
///
var autoTitle = true
var needAutoRefresh = true
private(set) lazy var webView: SRWebView = {
let controller = WKUserContentController()
let config = WKWebViewConfiguration()
config.userContentController = controller
config.preferences.javaScriptEnabled = true
/** JS */
config.preferences.javaScriptCanOpenWindowsAutomatically = true
let webView = SRWebView(frame: self.view.bounds, configuration: config)
webView.delegate = self
return webView
}()
override func viewDidLoad() {
super.viewDidLoad()
// self.edgesForExtendedLayout = []
fa_setupLayout()
if let url = webUrl {
self.load(webUrl: url)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
// self.fa_setNavigationStyle()
}
func load(webUrl: String) {
let str: String = webUrl
guard let url = URL(string: str) else { return }
let request = URLRequest(url: url, timeoutInterval: 30)
self.webView.load(request)
}
func reload() {
self.webView.reload()
}
}
extension SRBaseWebViewController {
private func fa_setupLayout() {
self.view.addSubview(webView)
self.webView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(0)
make.right.equalToSuperview().offset(0)
make.bottom.equalToSuperview().offset(0)
make.top.equalToSuperview().offset(UIScreen.navBarHeight)
}
}
}
//MARK: -------------- VPWebViewDelegate --------------
extension SRBaseWebViewController: SRWebViewDelegate {
func fa_webView(_ webView: SRWebView, shouldStartLoadWith navigationAction: WKNavigationAction) -> Bool {
self.webView.isHidden = false
return true
}
func fa_webViewDidStartLoad(_ webView: SRWebView) {
SRHud.show(containerView: self.view)
}
func fa_webView(webView: SRWebView, didChangeTitle title: String) {
if autoTitle {
self.title = title
}
}
func fa_webViewDidFinishLoad(_ webView: SRWebView) {
self.webView.isHidden = false
SRHud.dismiss()
}
func fa_webView(_ webView: SRWebView, didFailLoadWithError error: any Error) {
SRHud.dismiss()
}
func fa_userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
self.fa_webViewUserContentController(didReceive: message)
}
}

View File

@ -0,0 +1,26 @@
//
// SRWebMessageModel.swift
// SynthReel
//
// Created by CSGY on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
struct SRWebMessageModel: SmartCodable {
var type: String?
var data: SRWebMessageData?
}
struct SRWebMessageData: SmartCodable {
var activity_id: String?
var short_play_id: String?
var link: String?
}

View File

@ -0,0 +1,153 @@
//
// SRWebView.swift
// SynthReel
//
// Created by CSGY on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
@preconcurrency import WebKit
import YYText
@objc protocol SRWebViewDelegate: NSObjectProtocol {
@objc optional func fa_webView(_ webView: SRWebView, shouldStartLoadWith navigationAction: WKNavigationAction) -> Bool
@objc optional func fa_webViewDidStartLoad(_ webView: SRWebView)
@objc optional func fa_webViewDidFinishLoad(_ webView: SRWebView)
@objc optional func fa_webView(_ webView: SRWebView, didFailLoadWithError error: Error)
///
@objc optional func fa_webView(webView: SRWebView, didChangeProgress progress: CGFloat)
///
@objc optional func fa_webView(webView: SRWebView, didChangeTitle title: String)
///web
@objc optional func fa_userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
}
class SRWebView: WKWebView {
weak var delegate: SRWebViewDelegate?
private(set) var scriptMessageHandlerArray: [String] = [
kSRWebMessageAPP,
kSRWebMessageOpenFeedbackList,
kSRWebMessageOpenFeedbackDetail,
kSRWebMessageOpenPhotoPicker,
kSRWebMessageAccountDeletionFinish,
]
deinit {
self.removeObserver(self, forKeyPath: "estimatedProgress")
self.removeObserver(self, forKeyPath: "title")
}
override init(frame: CGRect, configuration: WKWebViewConfiguration) {
super.init(frame: frame, configuration: configuration)
addScriptMessageHandler()
_setupInit()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func _setupInit() {
self.isOpaque = false
self.navigationDelegate = self
self.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
self.addObserver(self, forKeyPath: "title", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object as? SRWebView == self {
if keyPath == "estimatedProgress", let progress = change?[NSKeyValueChangeKey.newKey] as? CGFloat {
self.delegate?.fa_webView?(webView: self, didChangeProgress: progress)
} else if keyPath == "title", let title = change?[NSKeyValueChangeKey.newKey] as? String {
self.delegate?.fa_webView?(webView: self, didChangeTitle: title)
}
}
}
func load(urlStr: String) {
guard let url = URL(string: urlStr) else { return }
let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad, timeoutInterval: 30)
self.load(request)
}
func removeScriptMessageHandler() {
self.scriptMessageHandlerArray.forEach{
configuration.userContentController.removeScriptMessageHandler(forName: $0)
}
}
func addScriptMessageHandler() {
self.scriptMessageHandlerArray.forEach{
configuration.userContentController.add(YYTextWeakProxy(target: self) as! WKScriptMessageHandler, name: $0)
}
}
}
//MARK:-------------- WKNavigationDelegate --------------
extension SRWebView: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
decisionHandler(.allow);
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url,
url.scheme != "http",
url.scheme != "https"
{
UIApplication.shared.open(url)
decisionHandler(.cancel)
return
}
if let result = self.delegate?.fa_webView?(self, shouldStartLoadWith: navigationAction) {
if result {
decisionHandler(.allow)
} else {
decisionHandler(.cancel)
}
} else {
decisionHandler(.allow)
}
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
self.delegate?.fa_webViewDidStartLoad?(self)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.delegate?.fa_webViewDidFinishLoad?(self)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
self.delegate?.fa_webView?(self, didFailLoadWithError: error)
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
self.delegate?.fa_webView?(self, didFailLoadWithError: error)
}
}
//MARK:-------------- WKScriptMessageHandler --------------
extension SRWebView: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
self.delegate?.fa_userContentController?(userContentController, didReceive: message)
}
}

View File

@ -0,0 +1,30 @@
//
// SRCoinPackController.swift
// SynthReel
//
// Created by CSGY on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRCoinPackController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}

View File

@ -0,0 +1,143 @@
//
// SRHomeHistoryBottomView.swift
// SynthReel
//
// Created by CSGY on 2025/11/27.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRHomeHistoryBottomView: UIView {
var model: SRShortModel? {
didSet {
coverImageView.sr_setImage(model?.image_url)
titleLabel.text = model?.name
let text = String(format: "EP. %@ / EP. %d", model?.current_episode ?? "0", model?.episode_total ?? 0)
setEpisodeText(text)
}
}
private lazy var coverImageView: SRImageView = {
let imageView = SRImageView()
imageView.layer.cornerRadius = 21
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var bgView: UIImageView = {
let view = UIImageView()
view.image = .recordBottomBg
return view
}()
private lazy var playView: UIImageView = {
let imageView = UIImageView(image: .recordBottomArrow)
imageView.setContentHuggingPriority(.required, for: .horizontal)
imageView.setContentCompressionResistancePriority(.required, for: .horizontal)
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .medium)
label.textColor = .white
return label
}()
private lazy var epLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 10, weight: .regular)
label.textColor = .white
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
fa_setupLayout()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleBg))
addGestureRecognizer(tap)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func handleBg() {
let vc = SRDetailPlayerViewController()
vc.shortId = self.model?.short_play_id
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}
func setEpisodeText(_ text: String) {
let full = text as NSString
let parts = text.components(separatedBy: " / ")
guard parts.count == 2 else {
epLabel.text = text
return
}
let front = parts[0] + " "
let frontLen = (front as NSString).length
let fullLen = full.length
let attr = NSMutableAttributedString(string: text)
//
attr.yy_setColor(.srBlue, range: NSRange(location: 0, length: frontLen))
attr.yy_setFont(.systemFont(ofSize: 12), range: NSRange(location: 0, length: frontLen))
//
attr.yy_setColor(UIColor.white, range: NSRange(location: frontLen, length: fullLen - frontLen))
attr.yy_setFont(.systemFont(ofSize: 12), range: NSRange(location: frontLen, length: fullLen - frontLen))
epLabel.attributedText = attr
}
}
extension SRHomeHistoryBottomView {
private func fa_setupLayout() {
addSubview(bgView)
addSubview(coverImageView)
bgView.addSubview(playView)
bgView.addSubview(titleLabel)
bgView.addSubview(epLabel)
bgView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalToSuperview().offset(1)
make.height.equalTo(57)
}
coverImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(25)
make.width.equalTo(42)
make.height.equalTo(42)
}
playView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.right.equalToSuperview().offset(-28)
}
titleLabel.snp.makeConstraints { make in
make.left.equalTo(coverImageView.snp.right).offset(10)
make.top.equalToSuperview().offset(12)
make.right.lessThanOrEqualTo(playView.snp.left).offset(-10)
}
epLabel.snp.makeConstraints { make in
make.left.equalTo(titleLabel)
make.bottom.equalToSuperview().offset(-12)
}
}
}

View File

@ -110,6 +110,13 @@ class SRHomeViewController: SRViewController {
return view
}()
private lazy var playHistoryView: SRHomeHistoryBottomView = {
let view = SRHomeHistoryBottomView()
view.isHidden = true
return view
}()
deinit {
srPrint(message: "销毁")
}
@ -121,7 +128,6 @@ class SRHomeViewController: SRViewController {
Task {
await requestModuleList()
// await requestCategoryList()
}
}
@ -140,6 +146,8 @@ class SRHomeViewController: SRViewController {
vc.handleHeaderRefresh(nil)
}
}
}
@ -153,6 +161,7 @@ extension SRHomeViewController {
menuContentView.addSubview(menuView)
menuContentView.addSubview(moreButton)
moreButton.addSubview(moreTitleLabel)
view.addSubview(playHistoryView)
// moreButton.addSubview(moreIconImageView)
menuView.listContainer = pageView.listContainerView
@ -197,6 +206,11 @@ extension SRHomeViewController {
make.left.equalToSuperview()
}
playHistoryView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().offset(-10)
}
// moreIconImageView.snp.makeConstraints { make in
// make.centerY.equalToSuperview()
// make.right.equalToSuperview()
@ -274,8 +288,17 @@ extension SRHomeViewController {
self.menuDataSource.titles = self.viewModel.categoryTitleArr
self.pageView.reloadData()
self.menuView.reloadData()
await self.requestHistory()
}
func requestHistory() async {
await self.viewModel.requestHistoryData()
if let playHistory = self.viewModel.historyModel {
self.playHistoryView.model = playHistory
self.playHistoryView.isHidden = false
} else {
self.playHistoryView.isHidden = true
}
}
}

View File

@ -13,6 +13,8 @@ class SRHomeViewModel: NSObject {
lazy var categoryArr: [SRShortModel] = []
lazy var categoryTitleArr: [String] = []
var historyModel: SRShortModel?
lazy var moduleArr: [SRHomeModuleItem] = []
// func requestCategoryList() async {
@ -26,6 +28,17 @@ class SRHomeViewModel: NSObject {
// }
// }
func requestHistoryData() async {
let listmodel = await SRHomeApi.requestHistoryData(page: 1)
if let model = listmodel?.first {
historyModel = model
} else {
historyModel = nil
}
}
func requestModuleList() async {
guard let list = await SRHomeApi.requestHomeModulesData() else { return }

View File

@ -0,0 +1,79 @@
//
// SRDetailRecommendCell.swift
// SynthReel
//
// Created by CSGY on 2025/11/27.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import FSPagerView
import JXPlayer
class SRDetailRecommendCell: FSPagerViewCell {
var model: SRShortModel? {
didSet {
player.coverImageView?.sr_setImage(model?.image_url)
player.setPlayUrl(url: model?.video_url ?? "")
}
}
///
private lazy var bgImageView: UIImageView = {
let iv = UIImageView()
iv.image = .homeViralHitsCell
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
return iv
}()
private lazy var player: JXPlayer = {
let player = JXPlayer(controlView: nil)
player.playerView = self.playerView
player.isLoop = true
return player
}()
private lazy var playerView: UIView = {
let view = UIView()
view.isUserInteractionEnabled = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(bgImageView)
bgImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
//
contentView.addSubview(playerView)
playerView.snp.makeConstraints { make in
// make.edges.equalToSuperview()
make.edges.equalTo(UIEdgeInsets(top: 15, left: 8, bottom: 15, right: 8))
}
// addSubview(playerView)
//
// playerView.snp.makeConstraints { make in
// make.edges.equalToSuperview()
// }
}
@MainActor required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func play() {
self.player.start()
}
func pause() {
self.player.pause()
}
}

View File

@ -0,0 +1,175 @@
//
// SRDetailRecommendview.swift
// SynthReel
//
// Created by CSGY on 2025/11/27.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import FSPagerView
class SRDetailRecommendview: SRBaseAlert {
var clickCloseButton: (() -> Void)?
var didSelectedVideo: ((_ model: SRShortModel) -> Void)?
var dataArr: [SRShortModel] = [] {
didSet {
self.pagerView.reloadData()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
self?.updateCurrentData()
}
}
}
private weak var currentCell: SRDetailRecommendCell? {
didSet {
oldValue?.pause()
currentCell?.play()
}
}
private lazy var titleLabel: SRLabel = {
let label = SRLabel()
label.font = .font(ofSize: 16, weight: .init(900))
label.textColors = [UIColor._4_CFFD_4.cgColor, UIColor._51_D_4_FF.cgColor]
label.textStartPoint = .init(x: 0.5, y: 0)
label.textEndPoint = .init(x: 0.5, y: 1)
label.text = "Keep the Drama Going".localized
return label
}()
private lazy var bgView: UIImageView = {
let view = UIImageView(image: UIImage(named: "recommendBg"))
view.isUserInteractionEnabled = true
return view
}()
private lazy var cancelButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "Close"), for: .normal)
button.addTarget(self, action: #selector(handleCancelButton), for: .touchUpInside)
return button
}()
private lazy var pagerView: FSPagerView = {
let transformer = SRPagerViewTransformer(type: .linear)
transformer.minimumScale = 1
let view = FSPagerView()
view.itemSize = .init(width: 132, height: 185)
view.transformer = transformer
view.delegate = self
view.dataSource = self
view.isInfinite = true
view.interitemSpacing = 8
view.register(SRDetailRecommendCell.self, forCellWithReuseIdentifier: "cell")
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentWidth = UIScreen.width
self.closeButton.isHidden = true
self.contentView.backgroundColor = .clear
self.contentView.layer.cornerRadius = 0
self.contentView.layer.masksToBounds = false
fa_setupLayout()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func handleCancelButton() {
self.dismiss()
self.clickCloseButton?()
}
///
private func updateCurrentData() {
guard let cell = self.pagerView.cellForItem(at: self.pagerView.currentIndex) as? SRDetailRecommendCell else { return }
self.currentCell = cell
// let model = cell.model
// self.videoNameLabel.text = model?.name
}
}
extension SRDetailRecommendview {
private func fa_setupLayout() {
contentView.addSubview(bgView)
contentView.addSubview(cancelButton)
bgView.addSubview(titleLabel)
bgView.addSubview(pagerView)
bgView.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(15)
make.top.equalToSuperview().offset(-25)
// make.bottom.e()
}
titleLabel.snp.makeConstraints { make in
make.top.equalTo(17)
make.centerX.equalToSuperview()
}
pagerView.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(10)
make.bottom.equalTo(-17)
make.top.equalTo(titleLabel.snp.bottom).offset(10)
}
cancelButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(bgView.snp.bottom).offset(30)
make.bottom.equalToSuperview()
}
}
}
//MARK: FSPagerViewDelegate FSPagerViewDataSource
extension SRDetailRecommendview: FSPagerViewDelegate, FSPagerViewDataSource {
func pagerView(_ pagerView: FSPagerView, cellForItemAt index: Int) -> FSPagerViewCell {
let cell = pagerView.dequeueReusableCell(withReuseIdentifier: "cell", at: index) as! SRDetailRecommendCell
cell.model = self.dataArr[index]
return cell
}
func numberOfItems(in pagerView: FSPagerView) -> Int {
return self.dataArr.count
}
func pagerView(_ pagerView: FSPagerView, didSelectItemAt index: Int) {
didSelectedVideo?(self.dataArr[index])
self.dismiss()
}
func pagerViewDidEndDecelerating(_ pagerView: FSPagerView) {
self.updateCurrentData()
}
}
extension SRDetailRecommendview {
class PageControl: UIPageControl{
override func size(forNumberOfPages pageCount: Int) -> CGSize {
return .init(width: 4, height: 4)
}
}
}

View File

@ -33,7 +33,7 @@ class SRDetailPlayerViewController: JXPlayerListViewController {
lazy var returnButton: UIButton = {
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
self.sr_handleNavigationBack()
self.handleBackButton()
}))
button.setImage(UIImage(named: "arrow_left_icon_01"), for: .normal)
return button
@ -54,6 +54,7 @@ class SRDetailPlayerViewController: JXPlayerListViewController {
Task {
await self.sr_viewModel.requestShortDetail()
await self.sr_viewModel.requsetRecommandData()
}
}
@ -75,6 +76,30 @@ class SRDetailPlayerViewController: JXPlayerListViewController {
await SRShortApi.requestCreatePlayHistory(shortId: videoInfo?.short_play_id, videoId: videoInfo?.short_play_video_id)
}
}
@objc private func handleBackButton() {
self.pause()
if !self.sr_viewModel.recommandList.isEmpty, self.sr_viewModel.isShowRecommand {
let view = SRDetailRecommendview()
view.dataArr = self.sr_viewModel.recommandList
view.clickCloseButton = { [weak self] in
self?.sr_handleNavigationBack()
}
view.didSelectedVideo = { [weak self] model in
guard let self = self else { return }
self.shortId = model.short_play_id
Task {
await self.sr_viewModel.requestShortDetail()
}
}
view.show(in: SRTool.keyWindow)
} else {
self.sr_handleNavigationBack()
}
// self.handleNavigationBack()
}
}
@ -115,3 +140,4 @@ extension SRDetailPlayerViewController: JXPlayerListViewControllerDelegate, JXPl
}
}
}

View File

@ -8,6 +8,7 @@
import UIKit
import JXPlayer
import YYText
import HWPanModal
class SRShortPlayerViewModel: JXPlayerListViewModel {
@ -16,6 +17,12 @@ class SRShortPlayerViewModel: JXPlayerListViewModel {
var dataArr: [SRShortDetailModel] = []
var recommandList: [SRShortModel] = []
///
private(set) var isShowRecommand = false
private var recommandTimer: Timer?
weak var popView: UIView?
nonisolated required init() {
@ -47,7 +54,10 @@ class SRShortPlayerViewModel: JXPlayerListViewModel {
targetIndexPath = .init(row: row, section: 0)
}
}
isShowRecommand = false
recommandTimer?.invalidate()
recommandTimer = nil
recommandTimer = Timer.scheduledTimer(timeInterval: 6, target: YYTextWeakProxy(target: self), selector: #selector(handleRecommandTimer), userInfo: nil, repeats: false)
self.playerListVC?.scrollToItem(indexPath: targetIndexPath, animated: false)
}
return code
@ -69,4 +79,15 @@ extension SRShortPlayerViewModel {
self.popView = view
}
func requsetRecommandData() async {
let (model, _, _) = await SRShortApi.requestRecommand()
guard let model = model else { return }
self.recommandList = model
}
@objc private func handleRecommandTimer() {
self.isShowRecommand = true
}
}

View File

@ -0,0 +1,91 @@
//
// SRFeedBackController.swift
// SynthReel
//
// Created by CSGY on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRFeedBackController: SRAppWebViewController {
private lazy var rightButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "feedList"), for: .normal)
button.addTarget(self, action: #selector(handleRightBarButton), for: .touchUpInside)
return button
}()
private lazy var redView: UIView = {
let view = UIView()
view.backgroundColor = .FF_9_F_4_C
view.layer.cornerRadius = 8
view.isHidden = true
return view
}()
private lazy var redLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .bold)
label.textColor = .white
return label
}()
override func viewDidLoad() {
self.webUrl = kSRFeedBackHomeWebUrl
super.viewDidLoad()
// self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "Frame 2085663258"), style: .plain, target: self, action: #selector(handleRightBarButton))
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: rightButton)
rightButton.addSubview(redView)
redView.addSubview(redLabel)
redView.snp.makeConstraints { make in
make.height.equalTo(16)
make.width.greaterThanOrEqualTo(16)
make.top.equalToSuperview().offset(-8)
make.right.equalToSuperview().offset(8)
}
redLabel.snp.makeConstraints { make in
make.center.equalToSuperview()
make.left.greaterThanOrEqualToSuperview().offset(3)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// self.requestRedCount()
}
@objc private func handleRightBarButton() {
let vc = SRAppWebViewController()
vc.webUrl = kSRFeedBackListWebUrl
self.navigationController?.pushViewController(vc, animated: true)
}
}
extension SRFeedBackController {
// private func requestRedCount() {
// FAAPI.requestFeedbackRedCount { [weak self] model in
// guard let self = self else { return }
// if let count = model?.feedback_notice_num, count > 0 {
// self.redView.isHidden = false
// self.redLabel.text = "\(count)"
// } else {
// self.redView.isHidden = true
// }
// }
//
// }
//
}

View File

@ -0,0 +1,21 @@
//
// SRRewardController.swift
// SynthReel
//
// Created by CSGY on 2025/11/28.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRRewardController: SRAppWebViewController {
override func viewDidLoad() {
self.webUrl = kSRRewardWebUrl
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}

View File

@ -96,6 +96,13 @@ extension SRUserViewController: UICollectionViewDelegate, UICollectionViewDataSo
let aboutvc = SRAboutUsController ();
self.navigationController?.pushViewController(aboutvc, animated: true)
break
case .feedback:
// let vc = SRFeedBackController ();
// self.navigationController?.pushViewController(vc, animated: true)
let vc = SRRewardController ()
vc.theme = "theme_15"
self.navigationController?.pushViewController(vc, animated: true)
break
case .privacyPolicy:
if let url = URL(string: SRWebBaseURL + "/private") {
UIApplication.shared.open(url)
@ -142,7 +149,7 @@ extension SRUserViewController {
private func setDataArr() {
let arr = [
// SRUserSettingModel(type: .feedback, name: "synthreel_feedback".localized, icon: UIImage(named: "icon_feedback")),
SRUserSettingModel(type: .feedback, name: "synthreel_feedback".localized, icon: UIImage(named: "icon_feedback")),
SRUserSettingModel(type: .about, name: "synthreel_about_us".localized, icon: UIImage(named: "icon_about")),
SRUserSettingModel(type: .privacyPolicy, name: "synthreel_privacy_policy".localized, icon: UIImage(named: "icon_privacy")),
SRUserSettingModel(type: .userAgreement, name: "synthreel_user_agreement".localized, icon: UIImage(named: "icon_user")),

View File

@ -23,8 +23,8 @@ struct SRUserSettingModel {
// case purchaseRecords
// ///
// case rewardCoins
// case deleteAccount
// case language
case deleteAccount
case language
}

View File

@ -0,0 +1,166 @@
//
// SRBaseAlert.swift
// SynthReel
//
// Created by CSGY on 2025/11/27.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRBaseAlert: UIView {
var clickHighlightButton: (() -> Void)?
var contentWidth: CGFloat = UIScreen.width - 70 {
didSet {
containerView.snp.updateConstraints { make in
make.width.equalTo(contentWidth)
}
}
}
private(set) var containerView: UIView = {
let view = UIView()
return view
}()
private(set) var contentView: UIView = {
let view = UIView()
view.backgroundColor = ._404040
view.layer.cornerRadius = 16
view.layer.masksToBounds = true
return view
}()
private(set) lazy var closeButton: UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "close_icon_03"), for: .normal)
button.addTarget(self, action: #selector(dismiss), for: .touchUpInside)
return button
}()
lazy var highlightButton: UIButton = {
let button = UIButton(type: .custom)
button.layer.cornerRadius = 16
button.layer.masksToBounds = true
button.setTitleColor(.srGreen, for: .normal)
button.setBackgroundImage(.suerButtonBg, for: .normal)
button.titleLabel?.font = .font(ofSize: 14, weight: .semibold)
button.addTarget(self, action: #selector(handleHighlightButton), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = ._000000.withAlphaComponent(0.5)
addSubview(containerView)
containerView.addSubview(contentView)
containerView.addSubview(closeButton)
containerView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(contentWidth)
}
contentView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.bottom.equalToSuperview().offset(-66)
}
closeButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@discardableResult
@objc func show(in view: UIView? = nil) -> Self {
guard self.superview == nil else { return self }
var inView: UIView
if let view = view {
inView = view
} else {
inView = SRBaseAlert.Window.manager.createWindow()
}
inView.addSubview(self)
self.frame = inView.bounds
showAnimation()
return self
}
@objc func dismiss() {
dismissAnimation()
}
@objc func handleHighlightButton() {
self.dismissAnimation()
self.clickHighlightButton?()
}
}
extension SRBaseAlert {
private func showAnimation() {
containerView.transform = CGAffineTransform(translationX: 0, y: 200)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0) {
self.containerView.transform = CGAffineTransform.identity
}
}
private func dismissAnimation() {
UIView.animate(withDuration: 0.3) {
self.alpha = 0
self.containerView.transform = CGAffineTransform(translationX: 0, y: 500)
} completion: { _ in
self.removeFromSuperview()
SRBaseAlert.Window.manager.dismissWindow()
}
}
}
extension SRBaseAlert {
class Window {
static let manager = Window()
private(set) var window: UIWindow?
func createWindow() -> UIWindow {
guard let window = window else {
let window = UIWindow(windowScene: SRTool.windowScene!)
window.backgroundColor = .clear
window.windowLevel = .alert
window.isHidden = false
self.window = window
return window
}
return window
}
func dismissWindow() {
guard let window = self.window else { return }
var isHidden = true
window.subviews.forEach {
if $0.isKind(of: SRBaseAlert.self) {
isHidden = false
}
}
if isHidden {
window.isHidden = true
self.window = nil
}
}
}
}

View File

@ -0,0 +1,20 @@
//
// SRPagerViewTransformer.swift
// SynthReel
//
// Created by CSGY on 2025/11/27.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import FSPagerView
class SRPagerViewTransformer: FSPagerViewTransformer {
override func proposedInteritemSpacing() -> CGFloat {
guard let pagerView = self.pagerView else {
return 0
}
return pagerView.interitemSpacing
}
}

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -23,13 +23,17 @@
"Select Episode" = "Select Episode";
"all_episodes_text" = "(All ## episodes)";
"recommend_ep_text" = "Watch the complete series";
"synthreel_feedback" = "Help Center";
"synthreel_about_us" = "About Us";
"synthreel_privacy_policy" = "Privacy Policy";
"synthreel_user_agreement" = "User Agreement";
"synthreel_visit_website" = "visit website";
"Viral Hits" = "Viral Hits";
"Live Trending Rankings" = "Live Trending Rankings";
"empty_title_01" = "No search results";
"empty_title_02" = "There is no data for the moment.";
"Keep the Drama Going" = "Keep the Drama Going";
"synthreel_feedback" = "FeedBack";
"synthreel_feedback_history" = "Feedback History";
"synthreel_feedback_detail" = "Feedback Details";
"synthreel_account_deletion" = "Account Deletion";
"Rewards" = "Rewards";