登陆功能

This commit is contained in:
HiBit 2025-12-02 13:27:27 +08:00
parent 2b3125d826
commit 5fb7a4d9bc
66 changed files with 1538 additions and 54 deletions

View File

@ -20,7 +20,7 @@ target 'SynthReel' do
pod 'YYCategories' pod 'YYCategories'
pod 'YYText' pod 'YYText'
pod 'Kingfisher' pod 'Kingfisher'
pod 'SmartCodable' pod 'SmartCodable','5.0.15'
pod 'Moya' pod 'Moya'
pod 'SVProgressHUD' pod 'SVProgressHUD'
pod 'Toast' pod 'Toast'

View File

@ -146,6 +146,24 @@
3754ACFC2ED9A36C009EBCAD /* SRCoinsPackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACFB2ED9A36C009EBCAD /* SRCoinsPackModel.swift */; }; 3754ACFC2ED9A36C009EBCAD /* SRCoinsPackModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACFB2ED9A36C009EBCAD /* SRCoinsPackModel.swift */; };
3754ACFE2ED9A3A0009EBCAD /* SRCoinsPackReceiveModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACFD2ED9A3A0009EBCAD /* SRCoinsPackReceiveModel.swift */; }; 3754ACFE2ED9A3A0009EBCAD /* SRCoinsPackReceiveModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754ACFD2ED9A3A0009EBCAD /* SRCoinsPackReceiveModel.swift */; };
3754AD022EDA8AF7009EBCAD /* SRLoginController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AD012EDA8AF7009EBCAD /* SRLoginController.swift */; }; 3754AD022EDA8AF7009EBCAD /* SRLoginController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AD012EDA8AF7009EBCAD /* SRLoginController.swift */; };
3754AD1A2EDD745A009EBCAD /* SRVideoLockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AD192EDD745A009EBCAD /* SRVideoLockView.swift */; };
3754AD1C2EDD77BA009EBCAD /* SRVideoUnlockResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AD1B2EDD77BA009EBCAD /* SRVideoUnlockResult.swift */; };
3754AD202EDD866F009EBCAD /* FacebookBasics in Frameworks */ = {isa = PBXBuildFile; productRef = 3754AD1F2EDD866F009EBCAD /* FacebookBasics */; };
3754AD222EDD866F009EBCAD /* FacebookCore in Frameworks */ = {isa = PBXBuildFile; productRef = 3754AD212EDD866F009EBCAD /* FacebookCore */; };
3754AD242EDD866F009EBCAD /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 3754AD232EDD866F009EBCAD /* FacebookLogin */; };
3754AD272EDD86FB009EBCAD /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = 3754AD262EDD86FB009EBCAD /* FirebaseAnalytics */; };
3754AD292EDD86FB009EBCAD /* FirebaseAnalyticsCore in Frameworks */ = {isa = PBXBuildFile; productRef = 3754AD282EDD86FB009EBCAD /* FirebaseAnalyticsCore */; };
3754AD2B2EDD86FB009EBCAD /* FirebaseCore in Frameworks */ = {isa = PBXBuildFile; productRef = 3754AD2A2EDD86FB009EBCAD /* FirebaseCore */; };
3754AD2D2EDD86FB009EBCAD /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = 3754AD2C2EDD86FB009EBCAD /* FirebaseCrashlytics */; };
3754AD2F2EDD86FB009EBCAD /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 3754AD2E2EDD86FB009EBCAD /* FirebaseMessaging */; };
3754AD312EDD939A009EBCAD /* SRUserLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AD302EDD939A009EBCAD /* SRUserLoginView.swift */; };
3754AD332EDD96E0009EBCAD /* SRLoginButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AD322EDD96E0009EBCAD /* SRLoginButtonView.swift */; };
3754AD362EDD9AA7009EBCAD /* SRLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AD352EDD9AA7009EBCAD /* SRLogin.swift */; };
3754AD382EDD9B5D009EBCAD /* SRTokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AD372EDD9B5D009EBCAD /* SRTokenModel.swift */; };
3754AD3A2EDD9BB1009EBCAD /* SRLogin+apple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AD392EDD9BB1009EBCAD /* SRLogin+apple.swift */; };
3754AD3C2EDD9C01009EBCAD /* SRThirdModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AD3B2EDD9C01009EBCAD /* SRThirdModel.swift */; };
3754AD3E2EDD9C88009EBCAD /* SRLogin+FB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AD3D2EDD9C88009EBCAD /* SRLogin+FB.swift */; };
3754AEF82EDE94CD009EBCAD /* SRUserRewardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3754AEF72EDE94CD009EBCAD /* SRUserRewardCell.swift */; };
3779D0612ECF1CB8006B1698 /* SRShortHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3779D0602ECF1CB8006B1698 /* SRShortHeaderView.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 */; }; 47BB39E2DD30787FA591F8EB /* Pods_SynthReel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9255BF4D4B1CFDDB5CFFB43 /* Pods_SynthReel.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -291,6 +309,17 @@
3754ACFB2ED9A36C009EBCAD /* SRCoinsPackModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCoinsPackModel.swift; sourceTree = "<group>"; }; 3754ACFB2ED9A36C009EBCAD /* SRCoinsPackModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCoinsPackModel.swift; sourceTree = "<group>"; };
3754ACFD2ED9A3A0009EBCAD /* SRCoinsPackReceiveModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCoinsPackReceiveModel.swift; sourceTree = "<group>"; }; 3754ACFD2ED9A3A0009EBCAD /* SRCoinsPackReceiveModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCoinsPackReceiveModel.swift; sourceTree = "<group>"; };
3754AD012EDA8AF7009EBCAD /* SRLoginController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRLoginController.swift; sourceTree = "<group>"; }; 3754AD012EDA8AF7009EBCAD /* SRLoginController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRLoginController.swift; sourceTree = "<group>"; };
3754AD192EDD745A009EBCAD /* SRVideoLockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRVideoLockView.swift; sourceTree = "<group>"; };
3754AD1B2EDD77BA009EBCAD /* SRVideoUnlockResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRVideoUnlockResult.swift; sourceTree = "<group>"; };
3754AD1D2EDD84DD009EBCAD /* SynthReel.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SynthReel.entitlements; sourceTree = "<group>"; };
3754AD302EDD939A009EBCAD /* SRUserLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRUserLoginView.swift; sourceTree = "<group>"; };
3754AD322EDD96E0009EBCAD /* SRLoginButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRLoginButtonView.swift; sourceTree = "<group>"; };
3754AD352EDD9AA7009EBCAD /* SRLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRLogin.swift; sourceTree = "<group>"; };
3754AD372EDD9B5D009EBCAD /* SRTokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRTokenModel.swift; sourceTree = "<group>"; };
3754AD392EDD9BB1009EBCAD /* SRLogin+apple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SRLogin+apple.swift"; sourceTree = "<group>"; };
3754AD3B2EDD9C01009EBCAD /* SRThirdModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRThirdModel.swift; sourceTree = "<group>"; };
3754AD3D2EDD9C88009EBCAD /* SRLogin+FB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SRLogin+FB.swift"; sourceTree = "<group>"; };
3754AEF72EDE94CD009EBCAD /* SRUserRewardCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRUserRewardCell.swift; sourceTree = "<group>"; };
3779D0602ECF1CB8006B1698 /* SRShortHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRShortHeaderView.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>"; }; 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>"; }; 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>"; };
@ -302,9 +331,17 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
3754AD272EDD86FB009EBCAD /* FirebaseAnalytics in Frameworks */,
03B1A84D2EC5DA43006C353F /* SnapKit in Frameworks */, 03B1A84D2EC5DA43006C353F /* SnapKit in Frameworks */,
3754AD222EDD866F009EBCAD /* FacebookCore in Frameworks */,
3754AD242EDD866F009EBCAD /* FacebookLogin in Frameworks */,
3754AD202EDD866F009EBCAD /* FacebookBasics in Frameworks */,
03B1A84A2EC5CE37006C353F /* ESTabBarController in Frameworks */, 03B1A84A2EC5CE37006C353F /* ESTabBarController in Frameworks */,
47BB39E2DD30787FA591F8EB /* Pods_SynthReel.framework in Frameworks */, 47BB39E2DD30787FA591F8EB /* Pods_SynthReel.framework in Frameworks */,
3754AD2D2EDD86FB009EBCAD /* FirebaseCrashlytics in Frameworks */,
3754AD2B2EDD86FB009EBCAD /* FirebaseCore in Frameworks */,
3754AD2F2EDD86FB009EBCAD /* FirebaseMessaging in Frameworks */,
3754AD292EDD86FB009EBCAD /* FirebaseAnalyticsCore in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -477,6 +514,7 @@
children = ( children = (
03B1A8EE2EC72C78006C353F /* SRShortModel.swift */, 03B1A8EE2EC72C78006C353F /* SRShortModel.swift */,
03B1A9272ECC05B1006C353F /* SRShortDetailModel.swift */, 03B1A9272ECC05B1006C353F /* SRShortDetailModel.swift */,
3754AD1B2EDD77BA009EBCAD /* SRVideoUnlockResult.swift */,
); );
path = M; path = M;
sourceTree = "<group>"; sourceTree = "<group>";
@ -511,6 +549,7 @@
03B1A94D2ECD604B006C353F /* SREpSelectorCell.swift */, 03B1A94D2ECD604B006C353F /* SREpSelectorCell.swift */,
3754ACD42ED82722009EBCAD /* SRDetailRecommendview.swift */, 3754ACD42ED82722009EBCAD /* SRDetailRecommendview.swift */,
3754ACD62ED82774009EBCAD /* SRDetailRecommendCell.swift */, 3754ACD62ED82774009EBCAD /* SRDetailRecommendCell.swift */,
3754AD192EDD745A009EBCAD /* SRVideoLockView.swift */,
); );
path = V; path = V;
sourceTree = "<group>"; sourceTree = "<group>";
@ -536,6 +575,7 @@
03E9A7C82EC47177000D1067 /* SynthReel */ = { 03E9A7C82EC47177000D1067 /* SynthReel */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
3754AD1D2EDD84DD009EBCAD /* SynthReel.entitlements */,
03E9A7D02EC471D6000D1067 /* Delegate */, 03E9A7D02EC471D6000D1067 /* Delegate */,
03E9A7D22EC47204000D1067 /* Base */, 03E9A7D22EC47204000D1067 /* Base */,
03E9A7D32EC4720F000D1067 /* Class */, 03E9A7D32EC4720F000D1067 /* Class */,
@ -599,6 +639,7 @@
03E9A7D42EC4764A000D1067 /* Libs */ = { 03E9A7D42EC4764A000D1067 /* Libs */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
3754AD342EDD9A8F009EBCAD /* SRLogin */,
3754ACD82ED83724009EBCAD /* FSPagerView */, 3754ACD82ED83724009EBCAD /* FSPagerView */,
3754ACCF2ED81F2F009EBCAD /* Alert */, 3754ACCF2ED81F2F009EBCAD /* Alert */,
3754ACC22ED6ED92009EBCAD /* Empty */, 3754ACC22ED6ED92009EBCAD /* Empty */,
@ -717,6 +758,9 @@
370D2F242ED5807600571E77 /* SRAboutHeaderVIew.swift */, 370D2F242ED5807600571E77 /* SRAboutHeaderVIew.swift */,
370D2F262ED581BB00571E77 /* SRAboutCell.swift */, 370D2F262ED581BB00571E77 /* SRAboutCell.swift */,
3754ACF32ED98609009EBCAD /* SRCoinPackCell.swift */, 3754ACF32ED98609009EBCAD /* SRCoinPackCell.swift */,
3754AD302EDD939A009EBCAD /* SRUserLoginView.swift */,
3754AD322EDD96E0009EBCAD /* SRLoginButtonView.swift */,
3754AEF72EDE94CD009EBCAD /* SRUserRewardCell.swift */,
); );
path = view; path = view;
sourceTree = "<group>"; sourceTree = "<group>";
@ -825,6 +869,18 @@
path = VC; path = VC;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
3754AD342EDD9A8F009EBCAD /* SRLogin */ = {
isa = PBXGroup;
children = (
3754AD352EDD9AA7009EBCAD /* SRLogin.swift */,
3754AD372EDD9B5D009EBCAD /* SRTokenModel.swift */,
3754AD392EDD9BB1009EBCAD /* SRLogin+apple.swift */,
3754AD3B2EDD9C01009EBCAD /* SRThirdModel.swift */,
3754AD3D2EDD9C88009EBCAD /* SRLogin+FB.swift */,
);
path = SRLogin;
sourceTree = "<group>";
};
3779D05F2ECF1C8D006B1698 /* V */ = { 3779D05F2ECF1C8D006B1698 /* V */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -904,6 +960,8 @@
packageReferences = ( packageReferences = (
03B1A8482EC5CE37006C353F /* XCRemoteSwiftPackageReference "ESTabBarController" */, 03B1A8482EC5CE37006C353F /* XCRemoteSwiftPackageReference "ESTabBarController" */,
03B1A84B2EC5DA43006C353F /* XCRemoteSwiftPackageReference "SnapKit" */, 03B1A84B2EC5DA43006C353F /* XCRemoteSwiftPackageReference "SnapKit" */,
3754AD1E2EDD866F009EBCAD /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */,
3754AD252EDD86FB009EBCAD /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
); );
preferredProjectObjectVersion = 77; preferredProjectObjectVersion = 77;
productRefGroup = 03E9A7A82EC4716A000D1067 /* Products */; productRefGroup = 03E9A7A82EC4716A000D1067 /* Products */;
@ -1041,11 +1099,13 @@
370D2F1E2ED54C7F00571E77 /* SRHelpCenterController.swift in Sources */, 370D2F1E2ED54C7F00571E77 /* SRHelpCenterController.swift in Sources */,
03E9A7CA2EC47177000D1067 /* SceneDelegate.swift in Sources */, 03E9A7CA2EC47177000D1067 /* SceneDelegate.swift in Sources */,
370D2F252ED5807600571E77 /* SRAboutHeaderVIew.swift in Sources */, 370D2F252ED5807600571E77 /* SRAboutHeaderVIew.swift in Sources */,
3754AD332EDD96E0009EBCAD /* SRLoginButtonView.swift in Sources */,
03B1A8402EC5CA37006C353F /* AppDelegate+Config.swift in Sources */, 03B1A8402EC5CA37006C353F /* AppDelegate+Config.swift in Sources */,
03B1A9152ECAEE63006C353F /* SRHomeViralHitsCell.swift in Sources */, 03B1A9152ECAEE63006C353F /* SRHomeViralHitsCell.swift in Sources */,
03B1A8472EC5CBCF006C353F /* SRViewController.swift in Sources */, 03B1A8472EC5CBCF006C353F /* SRViewController.swift in Sources */,
370D2F052ED3FEE700571E77 /* SRHistoryCell.swift in Sources */, 370D2F052ED3FEE700571E77 /* SRHistoryCell.swift in Sources */,
3754ACEA2ED94065009EBCAD /* SRBaseWebViewController + Script.swift in Sources */, 3754ACEA2ED94065009EBCAD /* SRBaseWebViewController + Script.swift in Sources */,
3754AD3A2EDD9BB1009EBCAD /* SRLogin+apple.swift in Sources */,
03B1A9032EC8555B006C353F /* SRHomeYouLikeView.swift in Sources */, 03B1A9032EC8555B006C353F /* SRHomeYouLikeView.swift in Sources */,
03B1A9362ECC1D1D006C353F /* SRSearchRecordView.swift in Sources */, 03B1A9362ECC1D1D006C353F /* SRSearchRecordView.swift in Sources */,
03B1A8D82EC6D051006C353F /* SRCollectionView.swift in Sources */, 03B1A8D82EC6D051006C353F /* SRCollectionView.swift in Sources */,
@ -1058,16 +1118,19 @@
03B1A83B2EC5C8E0006C353F /* SRToast.swift in Sources */, 03B1A83B2EC5C8E0006C353F /* SRToast.swift in Sources */,
03B1A9302ECC10D1006C353F /* SRSearchViewController.swift in Sources */, 03B1A9302ECC10D1006C353F /* SRSearchViewController.swift in Sources */,
03B1A8432EC5CB99006C353F /* SRTabBarController.swift in Sources */, 03B1A8432EC5CB99006C353F /* SRTabBarController.swift in Sources */,
3754AD3E2EDD9C88009EBCAD /* SRLogin+FB.swift in Sources */,
03B1A8EF2EC72C78006C353F /* SRShortModel.swift in Sources */, 03B1A8EF2EC72C78006C353F /* SRShortModel.swift in Sources */,
03B1A8ED2EC72C1F006C353F /* SRHomeModuleItem.swift in Sources */, 03B1A8ED2EC72C1F006C353F /* SRHomeModuleItem.swift in Sources */,
370D2F102ED4534500571E77 /* SRUserViewController.swift in Sources */, 370D2F102ED4534500571E77 /* SRUserViewController.swift in Sources */,
03B1A9112ECAC927006C353F /* SRHomeBingeWorthyCell.swift in Sources */, 03B1A9112ECAC927006C353F /* SRHomeBingeWorthyCell.swift in Sources */,
03E9A7F22EC4A8F6000D1067 /* UserDefaults+SRAdd.swift in Sources */, 03E9A7F22EC4A8F6000D1067 /* UserDefaults+SRAdd.swift in Sources */,
370D2F2B2ED597F700571E77 /* SRTopChartsCell.swift in Sources */, 370D2F2B2ED597F700571E77 /* SRTopChartsCell.swift in Sources */,
3754AD3C2EDD9C01009EBCAD /* SRThirdModel.swift in Sources */,
03B1A93A2ECC3F54006C353F /* SRSearchViewModel.swift in Sources */, 03B1A93A2ECC3F54006C353F /* SRSearchViewModel.swift in Sources */,
03E9A7F62EC4A9B1000D1067 /* SRUserInfo.swift in Sources */, 03E9A7F62EC4A9B1000D1067 /* SRUserInfo.swift in Sources */,
03B1A8F92EC813BC006C353F /* SRScrollView.swift in Sources */, 03B1A8F92EC813BC006C353F /* SRScrollView.swift in Sources */,
03B1A8E92EC721CD006C353F /* SRLabel.swift in Sources */, 03B1A8E92EC721CD006C353F /* SRLabel.swift in Sources */,
3754AD1A2EDD745A009EBCAD /* SRVideoLockView.swift in Sources */,
03B1A9282ECC05B1006C353F /* SRShortDetailModel.swift in Sources */, 03B1A9282ECC05B1006C353F /* SRShortDetailModel.swift in Sources */,
03980F592ECEED190006E317 /* SRMyShortViewController.swift in Sources */, 03980F592ECEED190006E317 /* SRMyShortViewController.swift in Sources */,
3754ACD32ED82113009EBCAD /* SRGradientbutton.swift in Sources */, 3754ACD32ED82113009EBCAD /* SRGradientbutton.swift in Sources */,
@ -1090,8 +1153,12 @@
03B1A9012EC852B2006C353F /* SRHomeModuleView.swift in Sources */, 03B1A9012EC852B2006C353F /* SRHomeModuleView.swift in Sources */,
03E9A7EF2EC4A8AF000D1067 /* SRAccountManager.swift in Sources */, 03E9A7EF2EC4A8AF000D1067 /* SRAccountManager.swift in Sources */,
03B1A8F52EC81277006C353F /* SRHomeBannerView.swift in Sources */, 03B1A8F52EC81277006C353F /* SRHomeBannerView.swift in Sources */,
3754AD312EDD939A009EBCAD /* SRUserLoginView.swift in Sources */,
3754AD1C2EDD77BA009EBCAD /* SRVideoUnlockResult.swift in Sources */,
3754ACDC2ED83F80009EBCAD /* SRHomeHistoryBottomView.swift in Sources */, 3754ACDC2ED83F80009EBCAD /* SRHomeHistoryBottomView.swift in Sources */,
3754AEF82EDE94CD009EBCAD /* SRUserRewardCell.swift in Sources */,
03B1A9132ECAED04006C353F /* SRHomeViralHitsView.swift in Sources */, 03B1A9132ECAED04006C353F /* SRHomeViralHitsView.swift in Sources */,
3754AD382EDD9B5D009EBCAD /* SRTokenModel.swift in Sources */,
03B1A8DC2EC6D0EB006C353F /* SRHomeChildCell.swift in Sources */, 03B1A8DC2EC6D0EB006C353F /* SRHomeChildCell.swift in Sources */,
03B1A8552EC5E434006C353F /* UIFont+SRAdd.swift in Sources */, 03B1A8552EC5E434006C353F /* UIFont+SRAdd.swift in Sources */,
3779D0612ECF1CB8006B1698 /* SRShortHeaderView.swift in Sources */, 3779D0612ECF1CB8006B1698 /* SRShortHeaderView.swift in Sources */,
@ -1108,6 +1175,7 @@
370D2F1C2ED4770800571E77 /* SRUserInfoModel.swift in Sources */, 370D2F1C2ED4770800571E77 /* SRUserInfoModel.swift in Sources */,
03B1A93C2ECC406E006C353F /* SRHotSearchView.swift in Sources */, 03B1A93C2ECC406E006C353F /* SRHotSearchView.swift in Sources */,
03B1A8E32EC6F577006C353F /* SRHomeMenuCell.swift in Sources */, 03B1A8E32EC6F577006C353F /* SRHomeMenuCell.swift in Sources */,
3754AD362EDD9AA7009EBCAD /* SRLogin.swift in Sources */,
3754ACE12ED93C4D009EBCAD /* SRCoinPackController.swift in Sources */, 3754ACE12ED93C4D009EBCAD /* SRCoinPackController.swift in Sources */,
3754ACD52ED82722009EBCAD /* SRDetailRecommendview.swift in Sources */, 3754ACD52ED82722009EBCAD /* SRDetailRecommendview.swift in Sources */,
); );
@ -1141,6 +1209,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = SynthReel/SynthReel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@ -1160,7 +1229,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.hbqinjiu.SynthReel; PRODUCT_BUNDLE_IDENTIFIER = com.drama.hive;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
@ -1184,6 +1253,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = SynthReel/SynthReel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@ -1201,7 +1271,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.hbqinjiu.SynthReel; PRODUCT_BUNDLE_IDENTIFIER = com.drama.hive;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
STRING_CATALOG_GENERATE_SYMBOLS = YES; STRING_CATALOG_GENERATE_SYMBOLS = YES;
@ -1380,6 +1450,22 @@
minimumVersion = 5.7.1; minimumVersion = 5.7.1;
}; };
}; };
3754AD1E2EDD866F009EBCAD /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/facebook/facebook-ios-sdk";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 14.1.0;
};
};
3754AD252EDD86FB009EBCAD /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/firebase/firebase-ios-sdk";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 12.6.0;
};
};
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
@ -1393,6 +1479,46 @@
package = 03B1A84B2EC5DA43006C353F /* XCRemoteSwiftPackageReference "SnapKit" */; package = 03B1A84B2EC5DA43006C353F /* XCRemoteSwiftPackageReference "SnapKit" */;
productName = SnapKit; productName = SnapKit;
}; };
3754AD1F2EDD866F009EBCAD /* FacebookBasics */ = {
isa = XCSwiftPackageProductDependency;
package = 3754AD1E2EDD866F009EBCAD /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */;
productName = FacebookBasics;
};
3754AD212EDD866F009EBCAD /* FacebookCore */ = {
isa = XCSwiftPackageProductDependency;
package = 3754AD1E2EDD866F009EBCAD /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */;
productName = FacebookCore;
};
3754AD232EDD866F009EBCAD /* FacebookLogin */ = {
isa = XCSwiftPackageProductDependency;
package = 3754AD1E2EDD866F009EBCAD /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */;
productName = FacebookLogin;
};
3754AD262EDD86FB009EBCAD /* FirebaseAnalytics */ = {
isa = XCSwiftPackageProductDependency;
package = 3754AD252EDD86FB009EBCAD /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseAnalytics;
};
3754AD282EDD86FB009EBCAD /* FirebaseAnalyticsCore */ = {
isa = XCSwiftPackageProductDependency;
package = 3754AD252EDD86FB009EBCAD /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseAnalyticsCore;
};
3754AD2A2EDD86FB009EBCAD /* FirebaseCore */ = {
isa = XCSwiftPackageProductDependency;
package = 3754AD252EDD86FB009EBCAD /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseCore;
};
3754AD2C2EDD86FB009EBCAD /* FirebaseCrashlytics */ = {
isa = XCSwiftPackageProductDependency;
package = 3754AD252EDD86FB009EBCAD /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseCrashlytics;
};
3754AD2E2EDD86FB009EBCAD /* FirebaseMessaging */ = {
isa = XCSwiftPackageProductDependency;
package = 3754AD252EDD86FB009EBCAD /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseMessaging;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
}; };
rootObject = 03E9A79F2EC4716A000D1067 /* Project object */; rootObject = 03E9A79F2EC4716A000D1067 /* Project object */;

View File

@ -1,6 +1,24 @@
{ {
"originHash" : "0e62262c59a183f44748a161870cc0f2b76e1b0e46f648559704e4be9de523b9", "originHash" : "deb3ea9bc9f7ea16d149d6025086ec795e4f5fc740540ae9e5e8308e4e3e8aec",
"pins" : [ "pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
"version" : "1.2024072200.0"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{ {
"identity" : "estabbarcontroller", "identity" : "estabbarcontroller",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
@ -10,6 +28,114 @@
"version" : "2.9.0" "version" : "2.9.0"
} }
}, },
{
"identity" : "facebook-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/facebook/facebook-ios-sdk",
"state" : {
"revision" : "c19607d535864533523d1f437c84035e5fb101cf",
"version" : "14.1.0"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk",
"state" : {
"revision" : "087bb95235f676c1a37e928769a5b6645dcbd325",
"version" : "12.6.0"
}
},
{
"identity" : "google-ads-on-device-conversion-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk",
"state" : {
"revision" : "35b601a60fbbea2de3ea461f604deaaa4d8bbd0c",
"version" : "3.2.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "c2d59acf17a8ba7ed80a763593c67c9c7c006ad1",
"version" : "12.5.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "60da361632d0de02786f709bdc0c4df340f7613e",
"version" : "8.1.0"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6",
"version" : "1.69.1"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "fb7f2740b1570d2f7599c6bb9531bf4fad6974b7",
"version" : "5.0.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
"version" : "101.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{ {
"identity" : "snapkit", "identity" : "snapkit",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
@ -18,6 +144,15 @@
"revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4", "revision" : "2842e6e84e82eb9a8dac0100ca90d9444b0307f4",
"version" : "5.7.1" "version" : "5.7.1"
} }
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "c169a5744230951031770e27e475ff6eefe51f9d",
"version" : "1.33.3"
}
} }
], ],
"version" : 3 "version" : 3

View File

@ -89,6 +89,24 @@ struct SRShortApi {
} }
} }
static func requestCoinUnlockVideo(shortId: String?, videoId: String?) async -> SRVideoUnlockResult? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/buy_video")
param.isToast = true
param.method = .post
param.parameters = [
"short_play_id": shortId ?? "",
"video_id": videoId ?? "0"
]
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRVideoUnlockResult>) in
continuation.resume(returning: response.data)
}
}
}
} }

View File

@ -22,4 +22,93 @@ struct SRUserApi {
} }
} }
} }
static func requestregister() async -> SRTokenModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/register")
param.method = .get
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRTokenModel>) in
continuation.resume(returning: response.data)
}
}
}
static func requestLeave() async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/leaveApp")
param.method = .post
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestEnterApp() async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/enterTheApp")
param.method = .post
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestStatOnLine() async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/onLine")
param.method = .post
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestStatApnsClick(id: String, title: String) async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/message/sendReport")
param.method = .post
param.parameters = [
"message_id" : id,
"title" : title
]
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestUploadApnsDeviceToken(token: String) async {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/onLine")
param.method = .post
param.parameters = [
"fcm_token": token
]
SRNetwork.request(parameters: param) { (_: SRNetwork.Response<String>) in
continuation.resume()
}
}
}
static func requestSignThirdLogin(model: SRThirdModel) async -> SRTokenModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/login")
param.method = .post
param.parameters = model.toDictionary()
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRTokenModel>) in
continuation.resume(returning: response.data)
}
}
}
static func requestLogout() async -> SRTokenModel? {
await withCheckedContinuation { continuation in
var param = SRNetwork.Parameters(path: "/customer/signout")
param.method = .post
SRNetwork.request(parameters: param) { (response: SRNetwork.Response<SRTokenModel>) in
continuation.resume(returning: response.data)
}
}
}
} }

View File

@ -30,7 +30,7 @@ extension SRNetwork {
var data: T? var data: T?
var msg: String? var msg: String?
@SmartIgnored @IgnoredKey
var rawData: Any? var rawData: Any?
var isSuccess: Bool { var isSuccess: Bool {

View File

@ -15,13 +15,16 @@ class SRPanModalContentView: HWPanModalContentView {
var contentHeight = UIScreen.height * (2 / 3) var contentHeight = UIScreen.height * (2 / 3)
var bgImage: UIImage? = UIImage(named: "pan_bg_image_01")
///UI contentSize ///UI contentSize
func setNeedsLayoutUpdate() { func setNeedsLayoutUpdate() {
self.panModalSetNeedsLayoutUpdate() self.panModalSetNeedsLayoutUpdate()
} }
lazy var bgImageView: UIImageView = { lazy var bgImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "pan_bg_image_01")) let imageView = UIImageView(image: bgImage)
return imageView return imageView
}() }()
@ -35,6 +38,18 @@ class SRPanModalContentView: HWPanModalContentView {
} }
} }
//
func updateBackgroundImage(_ img: UIImage?) {
self.bgImage = img
self.bgImageView.image = img
}
//
func updateContentHeight(_ height: CGFloat) {
self.contentHeight = height
self.panModalSetNeedsLayoutUpdate()
}
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }

View File

@ -32,7 +32,7 @@ class SRShortModel: NSObject, SmartCodable {
var category_name :String? var category_name :String?
var category_id :String? var category_id :String?
@SmartIgnored @IgnoredKey
var cellHeight: CGFloat = 0 var cellHeight: CGFloat = 0

View File

@ -0,0 +1,25 @@
//
// SRVideoUnlockResult.swift
// SynthReel
//
// Created by CSGY on 2025/12/1.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
struct SRVideoUnlockResult: SmartCodable {
enum Status: String, SmartCaseDefaultable {
///
case jump = "jump"
///
case noPlay = "no_play"
///
case notEnough = "not_enough"
///
case success = "success"
}
var status: Status?
}

View File

@ -15,6 +15,7 @@ class SREpSelectorCell: UICollectionViewCell {
var model: SRVideoInfoModel? { var model: SRVideoInfoModel? {
didSet { didSet {
numLabel.text = model?.episode numLabel.text = model?.episode
lockImageview.isHidden = !(model?.is_lock ?? true)
} }
} }
@ -48,6 +49,8 @@ class SREpSelectorCell: UICollectionViewCell {
return view return view
}() }()
lazy var lockImageview = UIImageView.init(image: .lock)
lazy var boderLayer: CAShapeLayer = { lazy var boderLayer: CAShapeLayer = {
let layer = CAShapeLayer() let layer = CAShapeLayer()
return layer return layer
@ -64,6 +67,7 @@ class SREpSelectorCell: UICollectionViewCell {
contentView.addSubview(boderView) contentView.addSubview(boderView)
boderView.layer.addSublayer(boderLayer) boderView.layer.addSublayer(boderLayer)
contentView.addSubview(numLabel) contentView.addSubview(numLabel)
contentView.addSubview(lockImageview)
numLabel.snp.makeConstraints { make in numLabel.snp.makeConstraints { make in
make.center.equalToSuperview() make.center.equalToSuperview()
@ -73,6 +77,12 @@ class SREpSelectorCell: UICollectionViewCell {
make.edges.equalToSuperview() make.edges.equalToSuperview()
} }
lockImageview.snp.makeConstraints { make in
make.right.top.equalToSuperview().inset(4)
make.width.height.equalTo(12)
}
} }

View File

@ -105,7 +105,6 @@ class SREpSelectorView: SRPanModalContentView {
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
sr_setupUI() sr_setupUI()
} }
@ -189,6 +188,21 @@ extension SREpSelectorView: UICollectionViewDelegate, UICollectionViewDataSource
} }
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let epList = self.model?.episodeList else { return }
if self.selectedIndex == indexPath.row { return }
let lastIndex = indexPath.row - 1
var lastIsLock = false
if lastIndex > 0 && lastIndex < epList.count {
let lastModel = epList[lastIndex]
lastIsLock = lastModel.is_lock ?? false
}
if lastIsLock {
SRToast.show(text: "buy_fail_toast_02".localized)
return
}
self.didSelected?(indexPath.row) self.didSelected?(indexPath.row)
Task { Task {
await self.dismiss(animated: true) await self.dismiss(animated: true)

View File

@ -116,6 +116,7 @@ class SRShortDetailControlView: JXPlayerListControlView {
return button return button
}() }()
lazy var playerImageView: UIImageView = { lazy var playerImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "play_icon_02")) let imageView = UIImageView(image: UIImage(named: "play_icon_02"))
imageView.isHidden = true imageView.isHidden = true
@ -191,7 +192,7 @@ extension SRShortDetailControlView {
addSubview(epButton) addSubview(epButton)
addSubview(collectButton) addSubview(collectButton)
addSubview(playerImageView) addSubview(playerImageView)
progressBgView.snp.makeConstraints { make in progressBgView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15) make.left.equalToSuperview().offset(15)
make.centerX.equalToSuperview() make.centerX.equalToSuperview()

View File

@ -23,16 +23,42 @@ class SRShortDetailPlayerCell: JXPlayerListCell {
return self.viewModel as? SRShortPlayerViewModel return self.viewModel as? SRShortPlayerViewModel
} }
var hasLastEpisodeUnlocked: Bool = false {
didSet {
self.lockView.hasLastEpisodeUnlocked = hasLastEpisodeUnlocked
}
}
private lazy var lockView: SRVideoLockView = {
let view = SRVideoLockView()
view.clickUnlockButton = { [weak self] in
Task {
await self?.sr_viewModel?.handleUnlockVideo()
}
}
return view
}()
override var model: Any? { override var model: Any? {
didSet { didSet {
let model = self.model as? SRVideoInfoModel let model = self.model as? SRVideoInfoModel
self.player.setPlayUrl(url: model?.video_url ?? "") self.player.setPlayUrl(url: model?.video_url ?? "")
// self.lockView.isHidden = !(model?.is_lock ?? true) self.lockView.isHidden = !(model?.is_lock ?? true)
// lockView.videoInfo = model lockView.videoInfo = model
} }
} }
override init(frame: CGRect) {
super.init(frame: frame)
sr_setupLayout()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var shortModel: SRShortModel? { var shortModel: SRShortModel? {
didSet { didSet {
self.sr_controlView.shortModel = shortModel self.sr_controlView.shortModel = shortModel
@ -41,3 +67,16 @@ class SRShortDetailPlayerCell: JXPlayerListCell {
} }
} }
extension SRShortDetailPlayerCell {
private func sr_setupLayout() {
addSubview(lockView)
lockView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}

View File

@ -0,0 +1,120 @@
//
// SRVideoLockView.swift
// SynthReel
//
// Created by CSGY on 2025/12/1.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRVideoLockView: UIView {
var clickUnlockButton: (() -> Void)?
var adUnlockButton: (() -> Void)?
var videoInfo: SRVideoInfoModel? {
didSet {
unlockButton.setNeedsUpdateConfiguration()
}
}
var hasLastEpisodeUnlocked = false {
didSet {
unlockButton.setNeedsUpdateConfiguration()
}
}
lazy var unlockStackView: UIStackView = {
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 12
stack.distribution = .fillEqually
return stack
}()
private lazy var unlockButton: UIButton = {
var config = UIButton.Configuration.plain()
config.image = UIImage(named: "lock")
config.imagePadding = 6
// 使 UIImage
config.background.image = UIImage(named: "unlockButtonBg")
config.background.imageContentMode = .scaleToFill //
let button = UIButton(configuration: config, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
self.clickUnlockButton?()
}))
button.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
let attributeContainer = AttributeContainer([
.font : UIFont.font(ofSize: 14, weight: .medium),
.foregroundColor : UIColor._51_D_4_FF
])
if hasLastEpisodeUnlocked {
button.configuration?.attributedTitle = .init("video_lock_tip_text".localized, attributes: attributeContainer)
} else {
button.configuration?.attributedTitle = .init("synthreel_unlocking_coins_notice".localizedReplace(text: "\(videoInfo?.coins ?? 0)"), attributes: attributeContainer)
}
}
return button
}()
private lazy var adlockButton: UIButton = {
var config = UIButton.Configuration.plain()
config.image = UIImage(named: "adlock")
config.imagePadding = 6
// 使 UIImage
config.background.image = UIImage(named: "unlockButtonBg")
config.background.imageContentMode = .scaleToFill //
let attr = AttributeContainer([
.font: UIFont.font(ofSize: 14, weight: .medium),
.foregroundColor: UIColor.white
])
config.attributedTitle = AttributedString("Watch 2ads to unlock".localized, attributes: attr)
let button = UIButton(configuration: config, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
self.adUnlockButton?()
}))
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = ._000000.withAlphaComponent(0.6)
sr_setupLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SRVideoLockView {
private func sr_setupLayout() {
addSubview(unlockStackView)
unlockStackView.addArrangedSubview(unlockButton)
unlockStackView.addArrangedSubview(adlockButton)
unlockStackView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(45)
make.right.equalToSuperview().offset(-45)
make.height.equalTo(43 * 2 + 30) // +
make.centerY.equalToSuperview() //
}
unlockButton.snp.makeConstraints { make in
make.height.equalTo(43)
}
adlockButton.snp.makeConstraints { make in
make.height.equalTo(43)
}
}
}

View File

@ -70,11 +70,31 @@ class SRDetailPlayerViewController: JXPlayerListViewController {
override func play() { override func play() {
let videoInfo = self.viewModel.currentCell?.model as? SRVideoInfoModel let videoInfo = self.viewModel.currentCell?.model as? SRVideoInfoModel
super.play()
Task { if videoInfo?.is_lock != true {
await SRShortApi.requestCreatePlayHistory(shortId: videoInfo?.short_play_id, videoId: videoInfo?.short_play_video_id) super.play()
Task {
await SRShortApi.requestCreatePlayHistory(shortId: videoInfo?.short_play_id, videoId: videoInfo?.short_play_video_id)
}
return
} }
self.pause()
let myCoins = SRAccountManager.manager.userInfo?.totalCoins ?? 0
let lockCoins = videoInfo?.coins ?? 0
if myCoins < lockCoins, (self.sr_viewModel.currentCell as? SRShortDetailPlayerCell)?.hasLastEpisodeUnlocked != true {
// self.sr_viewModel.openRechargeView()
} else {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let self = self else { return }
if (self.sr_viewModel.currentCell as? SRShortDetailPlayerCell)?.hasLastEpisodeUnlocked != true {
Task {
await self.sr_viewModel.handleUnlockVideo()
}
}
}
}
} }
@objc private func handleBackButton() { @objc private func handleBackButton() {
@ -123,6 +143,13 @@ extension SRDetailPlayerViewController: JXPlayerListViewControllerDelegate, JXPl
let cell = self.dequeueReusableCell(withReuseIdentifier: "SRShortDetailPlayerCell", for: indexPath) as! SRShortDetailPlayerCell let cell = self.dequeueReusableCell(withReuseIdentifier: "SRShortDetailPlayerCell", for: indexPath) as! SRShortDetailPlayerCell
cell.model = self.sr_viewModel.dataArr[indexPath.section].episodeList?[indexPath.row] cell.model = self.sr_viewModel.dataArr[indexPath.section].episodeList?[indexPath.row]
cell.shortModel = self.sr_viewModel.dataArr[indexPath.section].shortPlayInfo cell.shortModel = self.sr_viewModel.dataArr[indexPath.section].shortPlayInfo
let upRow = indexPath.row - 1
if upRow >= 0, let videoInfo = self.sr_viewModel.dataArr[indexPath.section].episodeList?[upRow], videoInfo.is_lock == true {
cell.hasLastEpisodeUnlocked = true
} else {
cell.hasLastEpisodeUnlocked = false
}
return cell return cell
} }

View File

@ -28,8 +28,9 @@ class SRShortPlayerViewModel: JXPlayerListViewModel {
nonisolated required init() { nonisolated required init() {
super.init() super.init()
} }
@MainActor
func requestShortDetail(indexPath: IndexPath? = nil) async -> Int? { func requestShortDetail(indexPath: IndexPath? = nil) async -> Int? {
let (model, code, _) = await SRShortApi.requestShortDetail(shortId) let (model, code, _) = await SRShortApi.requestShortDetail(shortId)
guard let model = model else { return code } guard let model = model else { return code }
@ -41,27 +42,67 @@ class SRShortPlayerViewModel: JXPlayerListViewModel {
guard let self = self else { return } guard let self = self else { return }
var targetIndexPath = IndexPath(row: 0, section: 0) var targetIndexPath = IndexPath(row: 0, section: 0)
if let indexPath = indexPath, indexPath.row < (model.episodeList?.count ?? 0) { if let indexPath = indexPath,
indexPath.row < (model.episodeList?.count ?? 0) {
targetIndexPath = indexPath targetIndexPath = indexPath
} else if let videoInfo = model.video_info { } else if let videoInfo = model.video_info {
var row: Int? if let row = model.episodeList?.firstIndex(where: {
model.episodeList?.enumerated().forEach { $0.short_play_video_id == videoInfo.short_play_video_id
if $1.short_play_video_id == videoInfo.short_play_video_id { }) {
row = $0 targetIndexPath = IndexPath(row: row, section: 0)
}
}
if let row = row {
targetIndexPath = .init(row: row, section: 0)
} }
} }
isShowRecommand = false isShowRecommand = false
recommandTimer?.invalidate() recommandTimer?.invalidate()
recommandTimer = nil recommandTimer = nil
recommandTimer = Timer.scheduledTimer(timeInterval: 6, target: YYTextWeakProxy(target: self), selector: #selector(handleRecommandTimer), userInfo: nil, repeats: false) recommandTimer = Timer.scheduledTimer(
timeInterval: 6,
target: YYTextWeakProxy(target: self),
selector: #selector(handleRecommandTimer),
userInfo: nil,
repeats: false
)
self.playerListVC?.scrollToItem(indexPath: targetIndexPath, animated: false) self.playerListVC?.scrollToItem(indexPath: targetIndexPath, animated: false)
} }
return code return code
} }
@MainActor
private func unlockVideo(completer: ((_ finish: Bool) -> Void)?) async {
guard let videoInfo = self.currentCell?.model as? SRVideoInfoModel else { return }
guard let shortPlayId = videoInfo.short_play_id else { return }
guard let videoId = videoInfo.short_play_video_id else { return }
let (model) = await SRShortApi.requestCoinUnlockVideo(shortId: shortPlayId,videoId: videoId)
guard let model = model else {
completer?(false)
return
}
switch model.status {
case .jump:
SRToast.show(text: "buy_fail_toast_02".localized)
case .noPlay:
SRToast.show(text: "buy_fail_toast_01".localized)
case .notEnough:
break
//广
// self.openRechargeView()
default: break
}
if model.status == .success {
Task {
await SRAccountManager.manager.updateUserInfo()
videoInfo.is_lock = false
completer?(true)
}
} else {
completer?(false)
}
}
} }
@ -85,6 +126,21 @@ extension SRShortPlayerViewModel {
self.recommandList = model self.recommandList = model
} }
@MainActor
func handleUnlockVideo() async {
await unlockVideo { [weak self] finish in
guard let self = self else { return }
if finish {
SRToast.show(text: "synthreel_success".localized)
self.playerListVC?.reloadData {
self.playerListVC?.play()
}
}
}
}
@objc private func handleRecommandTimer() { @objc private func handleRecommandTimer() {
self.isShowRecommand = true self.isShowRecommand = true
} }

View File

@ -17,14 +17,6 @@ class SRHelpCenterController: UIViewController {
} }
/*
// 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

@ -15,16 +15,6 @@ class SRPrivacyController: UIViewController {
// Do any additional setup after loading the view. // 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

@ -15,7 +15,4 @@ class SRRewardController: SRAppWebViewController {
super.viewDidLoad() super.viewDidLoad()
// Do any additional setup after loading the view. // Do any additional setup after loading the view.
} }
} }

View File

@ -26,6 +26,7 @@ class SRUserViewController: SRViewController {
collectionView.register(SRUserSettingCell.self, forCellWithReuseIdentifier: "cell") collectionView.register(SRUserSettingCell.self, forCellWithReuseIdentifier: "cell")
collectionView.register(SRUserTopCell.self, forCellWithReuseIdentifier: "topcell") collectionView.register(SRUserTopCell.self, forCellWithReuseIdentifier: "topcell")
collectionView.register(SRCoinPackCell.self, forCellWithReuseIdentifier: "coincell") collectionView.register(SRCoinPackCell.self, forCellWithReuseIdentifier: "coincell")
collectionView.register(SRUserRewardCell.self, forCellWithReuseIdentifier: "rewardCell")
collectionView.sr_addRefreshHeader { [weak self] in collectionView.sr_addRefreshHeader { [weak self] in
Task { Task {
@ -44,6 +45,8 @@ class SRUserViewController: SRViewController {
setDataArr() setDataArr()
// Do any additional setup after loading the view. // Do any additional setup after loading the view.
} }
} }
@ -67,8 +70,12 @@ extension SRUserViewController: UICollectionViewDelegate, UICollectionViewDataSo
} else if indexPath.section == 1 { } else if indexPath.section == 1 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "coincell", for: indexPath) as! SRCoinPackCell let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "coincell", for: indexPath) as! SRCoinPackCell
return cell return cell
}else if indexPath.section == 2 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "rewardCell", for: indexPath) as! SRUserRewardCell
return cell
} }
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SRUserSettingCell let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! SRUserSettingCell
cell.model = self.dataArr[indexPath.row]; cell.model = self.dataArr[indexPath.row];
return cell return cell
@ -102,6 +109,10 @@ extension SRUserViewController: UICollectionViewDelegate, UICollectionViewDataSo
let aboutvc = SRAboutUsController (); let aboutvc = SRAboutUsController ();
self.navigationController?.pushViewController(aboutvc, animated: true) self.navigationController?.pushViewController(aboutvc, animated: true)
break break
case .login:
let loginview = SRUserLoginView()
loginview.present(in: nil)
break
case .feedback: case .feedback:
// let vc = SRFeedBackController (); // let vc = SRFeedBackController ();
// self.navigationController?.pushViewController(vc, animated: true) // self.navigationController?.pushViewController(vc, animated: true)
@ -157,6 +168,7 @@ extension SRUserViewController {
let arr = [ 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: .login, name: "synthreel_login".localized, icon: UIImage(named: "icon_login")),
SRUserSettingModel(type: .about, name: "synthreel_about_us".localized, icon: UIImage(named: "icon_about")), 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: .privacyPolicy, name: "synthreel_privacy_policy".localized, icon: UIImage(named: "icon_privacy")),
SRUserSettingModel(type: .userAgreement, name: "synthreel_user_agreement".localized, icon: UIImage(named: "icon_user")), SRUserSettingModel(type: .userAgreement, name: "synthreel_user_agreement".localized, icon: UIImage(named: "icon_user")),

View File

@ -17,12 +17,14 @@ struct SRUserSettingModel {
case privacyPolicy case privacyPolicy
case userAgreement case userAgreement
case visitWebsite case visitWebsite
case login
case logout
// /// // ///
// case consumptionRecords case consumptionRecords
// /// // ///
// case purchaseRecords case purchaseRecords
// /// // ///
// case rewardCoins case rewardCoins
case deleteAccount case deleteAccount
case language case language
} }

View File

@ -0,0 +1,66 @@
//
// SRLoginButtonView.swift
// SynthReel
//
// Created by CSGY on 2025/12/1.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRLoginButtonView: UIControl {
private let bgImageView = UIImageView()
private let iconView = UIImageView()
private lazy var titleLabel: SRLabel = {
let label = SRLabel()
label.font = .font(ofSize: 14, weight: .medium)
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)
return label
}()
private let arrowView = UIImageView(image: UIImage(named: "arrow_right_icon_01"))
init(icon: UIImage?, title: String, background: UIImage?) {
super.init(frame: .zero)
bgImageView.image = background
iconView.image = icon
titleLabel.text = title
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
addSubview(bgImageView)
addSubview(iconView)
addSubview(titleLabel)
addSubview(arrowView)
bgImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
iconView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(10)
make.centerY.equalToSuperview()
make.width.height.equalTo(20)
}
titleLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.centerX.equalToSuperview()
}
arrowView.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-16)
make.centerY.equalToSuperview()
make.width.equalTo(10)
make.height.equalTo(10)
}
}
}

View File

@ -0,0 +1,84 @@
//
// SRUserLoginView.swift
// SynthReel
//
// Created by CSGY on 2025/12/1.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRUserLoginView: SRPanModalContentView {
private let fbLoginView = SRLoginButtonView(
icon: UIImage(named: "fb_icon"),
title: "Login With Facebook".localized,
background: UIImage(named: "FacebookBg")
)
private let appleLoginView = SRLoginButtonView(
icon: UIImage(named: "apple_icon"),
title: "Login With Apple".localized,
background: UIImage(named: "appleBg")
)
override init(frame: CGRect) {
super.init(frame: frame)
sr_setupUI()
self.updateContentHeight(246)
self.updateBackgroundImage(.loginBg)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension SRUserLoginView {
func sr_setupUI() {
addSubview(fbLoginView)
addSubview(appleLoginView)
fbLoginView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(35)
make.left.equalToSuperview().offset(48)
make.right.equalToSuperview().offset(-48)
make.height.equalTo(48)
}
appleLoginView.snp.makeConstraints { make in
make.top.equalTo(fbLoginView.snp.bottom).offset(20)
make.left.right.height.equalTo(fbLoginView)
}
//
fbLoginView.addTarget(self, action: #selector(tapFB), for: .touchUpInside)
appleLoginView.addTarget(self, action: #selector(tapApple), for: .touchUpInside)
}
@objc func tapFB() {
self.login(type: .faceBook)
}
@objc func tapApple() {
self.login(type: .apple)
}
private func login(type: SRLogin.LoginType) {
SRHud.show()
SRLogin.manager.thirdLogin(type: type, presentingViewController: nil) { [weak self] isFinish in
SRHud.dismiss()
guard let self = self else { return }
if isFinish {
Task {
await self.dismiss(animated: true)
}
}
}
}
}

View File

@ -0,0 +1,13 @@
//
// SRUserRewardCell.swift
// SynthReel
//
// Created by CSGY on 2025/12/2.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
class SRUserRewardCell: UICollectionViewCell {
}

View File

@ -52,7 +52,7 @@ extension SRUserSettingCell {
} }
titleLabel.snp.makeConstraints { make in titleLabel.snp.makeConstraints { make in
make.left.equalTo(iconImage.snp_rightMargin).offset(10) make.left.equalTo(iconImage.snp.right).offset(10)
make.centerY.equalToSuperview() make.centerY.equalToSuperview()
} }

View File

@ -7,12 +7,15 @@
// //
import UIKit import UIKit
import FacebookCore
extension AppDelegate { extension AppDelegate {
func setConfig() { func setConfig() {
SRToast.config() SRToast.config()
} }
func sr_registThirdparty(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
}
} }

View File

@ -16,7 +16,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
SRTool.appDelegate = self SRTool.appDelegate = self
SRNetworkReachableManager.manager.startMonitoring() SRNetworkReachableManager.manager.startMonitoring()
sr_registThirdparty(application, didFinishLaunchingWithOptions: launchOptions)
self.setConfig() self.setConfig()
@ -24,6 +24,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
await SRAccountManager.manager.updateUserInfo() await SRAccountManager.manager.updateUserInfo()
} }
return true return true
} }

View File

@ -0,0 +1,60 @@
//
// SRLogin+FB.swift
// SynthReel
//
// Created by CSGY on 2025/12/1.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import FacebookLogin
extension SRLogin {
func facebookLogin(presentingViewController: UIViewController?, completer: ((_ model: SRThirdModel?) -> Void)?) {
let loginManager = LoginManager()
loginManager.logOut()
loginManager.defaultAudience = .everyone
loginManager.logIn(permissions: ["public_profile", "email"], from: presentingViewController) { result, error in
guard error == nil, let result = result else {
completer?(nil)
return
}
if result.isCancelled {
completer?(nil)
return
}
let request = GraphRequest(graphPath: "me", parameters: ["fields" : "id,name,email,picture"], httpMethod: .get)
request.start { connection, result, error in
guard let result = result as? [String : Any] else {
completer?(nil)
return
}
var model = SRThirdModel()
model.platform = .faceBook
model.third_id = result["id"] as? String
model.email = result["email"] as? String
if let picture = result["picture"] as? [String : Any],
let data = picture["data"] as? [String : Any],
let url = data["url"] as? String
{
model.avator = url
}
if let name = result["name"] as? String {
model.family_name = name
} else {
model.family_name = result["first_name"] as? String
model.giving_name = result["last_name"] as? String
}
completer?(model)
}
}
}
}

View File

@ -0,0 +1,116 @@
//
// SRLogin+third.swift
// SynthReel
//
// Created by CSGY on 2025/12/1.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import AuthenticationServices
extension SRLogin {
private struct AssociatedKeys {
static var appleLoginHandle: Int?
}
private var appleLoginHandle: ((_ model: SRThirdModel?) -> Void)? {
set {
objc_setAssociatedObject(self, &AssociatedKeys.appleLoginHandle, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
get {
return objc_getAssociatedObject(self, &AssociatedKeys.appleLoginHandle) as? ((_ model: SRThirdModel?) -> Void)
}
}
func appleLogin(completer: ((_ model: SRThirdModel?) -> Void)?) {
self.appleLoginHandle = completer
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
private func jwtDecode(jwtStr: String) -> [String: Any]? {
let segments = jwtStr.components(separatedBy: ".")
guard segments.count > 1 else { return nil }
var base64String = segments[1]
// Base64
let requiredLength = 4 * Int(ceil(Double(base64String.count) / 4.0))
let paddingLength = requiredLength - base64String.count
if paddingLength > 0 {
base64String += String(repeating: "=", count: paddingLength)
}
// URL
base64String = base64String.replacingOccurrences(of: "-", with: "+")
base64String = base64String.replacingOccurrences(of: "_", with: "/")
// Base64
guard let data = Data(base64Encoded: base64String),
let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []),
let payload = jsonObject as? [String: Any] else {
return nil
}
return payload
}
}
//MARK: ASAuthorizationControllerDelegate
extension SRLogin: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
let userIdentifier = appleIDCredential.user
let fullName = appleIDCredential.fullName
let email = appleIDCredential.email
let identityToken = appleIDCredential.identityToken.flatMap { String(data: $0, encoding: .utf8) }
let identityTokenParams = self.jwtDecode(jwtStr: identityToken ?? "")
var model = SRThirdModel()
model.platform = .apple
model.third_id = userIdentifier
model.giving_name = fullName?.givenName
model.family_name = fullName?.familyName
model.avator = identityTokenParams?["picture"] as? String
model.email = identityTokenParams?["email"] as? String
debugLog(userIdentifier)
debugLog(fullName)
debugLog(email)
appleLoginHandle?(model)
appleLoginHandle = nil
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
appleLoginHandle?(nil)
appleLoginHandle = nil
}
}
//MARK: ASAuthorizationControllerPresentationContextProviding
extension SRLogin: ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return SRTool.keyWindow!
}
}

View File

@ -0,0 +1,133 @@
//
// SRLogin.swift
// SynthReel
//
// Created by CSGY on 2025/12/1.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
class SRLogin: NSObject {
enum LoginType: String, SmartCaseDefaultable {
case apple = "Apple"
case faceBook = "Facebook"
case google = "Google"
case tiktok = "Tiktok"
}
static let manager = SRLogin()
private(set) var token = UserDefaults.sr_object(forKey: kSRAccountTokenDefaultsKey, as: SRTokenModel.self)
private(set) var userInfo = UserDefaults.sr_object(forKey: kSRUserInfoDefaultsKey, as: SRUserInfo.self)
var isLogin: Bool {
return !(userInfo?.is_tourist ?? true)
}
private func setToken(_ token: SRTokenModel?) {
self.token = token
UserDefaults.sr_setObject(token, forKey: kSRAccountTokenDefaultsKey)
}
private func setUserInfo(_ userInfo: SRUserInfo?) {
self.userInfo = userInfo
UserDefaults.sr_setObject(userInfo, forKey: kSRUserInfoDefaultsKey)
}
///
func thirdLogin(type: LoginType, presentingViewController: UIViewController?, completer: ((_ isFinish: Bool) -> Void)?) {
switch type {
case .apple:
appleLogin { [weak self] model in
self?.requestSignThirdLogin(thirdSignModel: model, completer: completer)
}
case .faceBook:
facebookLogin(presentingViewController: presentingViewController) { [weak self] model in
self?.requestSignThirdLogin(thirdSignModel: model, completer: completer)
}
default:
break
}
}
///
private func requestSignThirdLogin(thirdSignModel: SRThirdModel?, completer: ((_ isFinish: Bool) -> Void)?) {
guard let thirdSignModel = thirdSignModel else {
completer?(false)
return
}
Task {
await SRUserApi.requestLeave()
let tokenModel = await SRUserApi.requestSignThirdLogin(model: thirdSignModel)
guard let token = tokenModel else {
completer?(false)
return
}
self.setToken(token)
self.userInfo?.is_tourist = false
await self.requestUserInfo(completer: nil)
await SRUserApi.requestEnterApp()
await SRUserApi.requestStatOnLine()
completer?(true)
await MainActor.run {
NotificationCenter.default.post(name: SRLogin.userInfoUpdateNotification, object: nil)
NotificationCenter.default.post(name: SRLogin.loginStatusChangeNotification, object: nil)
}
}
}
func logout(completer: ((_ isFinish: Bool) -> Void)?) async {
await SRUserApi.requestLeave()
let tokenModel = await SRUserApi.requestLogout()
guard let token = tokenModel else {
completer?(false)
return
}
self.setToken(token)
self.userInfo?.is_tourist = true
await self.requestUserInfo(completer: nil)
await SRUserApi.requestEnterApp()
await SRUserApi.requestStatOnLine()
completer?(true)
await MainActor.run {
NotificationCenter.default.post(name: SRLogin.userInfoUpdateNotification, object: nil)
NotificationCenter.default.post(name: SRLogin.loginStatusChangeNotification, object: nil)
}
}
}
extension SRLogin {
func requestUserInfo(completer: (() -> Void)?) async {
let userInfo = await SRUserApi.requestUserInfo()
if let user = userInfo {
self.setUserInfo(user)
}
completer?()
}
func requestUserToken(completer: (() -> Void)?) async {
let tokenModel = await SRUserApi.requestregister()
if let token = tokenModel {
self.setToken(token)
}
completer?()
NotificationCenter.default.post(name: Self.userInfoUpdateNotification, object: nil)
}
}
extension SRLogin {
///
@objc static let userInfoUpdateNotification = NSNotification.Name(rawValue: "FALogin.userInfoUpdateNotification")
///
@objc static let loginStatusChangeNotification = NSNotification.Name(rawValue: "FALogin.loginStatusChangeNotification")
}

View File

@ -0,0 +1,24 @@
//
// SRThirdModel.swift
// SynthReel
//
// Created by CSGY on 2025/12/1.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
struct SRThirdModel: SmartCodable {
var third_id: String?
var email: String?
//
var family_name: String?
//
var giving_name: String?
var avator: String?
var platform: SRLogin.LoginType?
}

View File

@ -0,0 +1,37 @@
//
// SRTokenModel.swift
// SynthReel
//
// Created by CSGY on 2025/12/1.
// Copyright © 2025 SR. All rights reserved.
//
import UIKit
import SmartCodable
class SRTokenModel: NSObject,SmartCodable, NSSecureCoding {
var auto_login: Int?
var customer_id: String?
var token: String?
required override init() { }
static var supportsSecureCoding: Bool {
return true
}
func encode(with coder: NSCoder) {
coder.encode(token, forKey: "token")
coder.encode(customer_id, forKey: "customer_id")
coder.encode(auto_login, forKey: "auto_login")
}
required init?(coder: NSCoder) {
super.init()
token = coder.decodeObject(of: NSString.self, forKey: "token") as? String
customer_id = coder.decodeObject(of: NSString.self, forKey: "customer_id") as? String
auto_login = coder.decodeObject(of: NSNumber.self, forKey: "auto_login")?.intValue
}
}

View File

@ -8,6 +8,14 @@
import UIKit import UIKit
#if DEBUG
func debugLog(_ msg: Any, file: String = #file, function: String = #function, line: Int = #line) {
print("\n\(Date(timeIntervalSinceNow: 8 * 60 * 60)) \(file.components(separatedBy: "/").last ?? "") \(function) \(line): \(msg)")
}
#else
func debugLog(_ msg: Any) { }
#endif
class SRTool { class SRTool {
static var appDelegate: AppDelegate? static var appDelegate: AppDelegate?

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1004 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@ -2,8 +2,29 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>UIDesignRequiresCompatibility</key> <key>CFBundleURLTypes</key>
<true/> <array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>synthreel</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>fb1765365954312054</string>
</array>
</dict>
</array>
<key>FacebookAppID</key>
<string>1765365954312054</string>
<key>FacebookClientToken</key>
<string>7a0b78de0a5f0e04738e8596b1b50aaa</string>
<key>UIApplicationSceneManifest</key> <key>UIApplicationSceneManifest</key>
<dict> <dict>
<key>UIApplicationSupportsMultipleScenes</key> <key>UIApplicationSupportsMultipleScenes</key>
@ -21,5 +42,12 @@
</array> </array>
</dict> </dict>
</dict> </dict>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UIDesignRequiresCompatibility</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -34,9 +34,19 @@
"Keep the Drama Going" = "Keep the Drama Going"; "Keep the Drama Going" = "Keep the Drama Going";
"synthreel_feedback" = "FeedBack"; "synthreel_feedback" = "FeedBack";
"synthreel_feedback_history" = "Feedback History"; "synthreel_feedback_history" = "Feedback History";
"synthreel_login" = "login";
"synthreel_logout" = "logout";
"synthreel_feedback_detail" = "Feedback Details"; "synthreel_feedback_detail" = "Feedback Details";
"synthreel_account_deletion" = "Account Deletion"; "synthreel_account_deletion" = "Account Deletion";
"Rewards" = "Rewards"; "Rewards" = "Rewards";
"Daily reward ready !" = "Daily reward ready !"; "Daily reward ready !" = "Daily reward ready !";
"Claim your rewards now" = "Claim your rewards now"; "Claim your rewards now" = "Claim your rewards now";
"My Refills" = "My Refills"; "My Refills" = "My Refills";
"synthreel_unlocking_coins_notice" = "Unlock";
"video_lock_tip_text" = "Pre.locked";
"Watch 2ads to unlock" = "Watch 2ads to unlock";
"synthreel_success" = "success";
"buy_fail_toast_01" = "Purchase failed, please try again later!";
"buy_fail_toast_02" = "The prequel to this series is not unlocked. Please unlock the prequel before unlocking this series";
"Login With Facebook" = "Login With Facebook";
"Login With Apple" = "Login With Apple";

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>