diff --git a/.gitignore b/.gitignore index 8a981e9..74f3634 100644 --- a/.gitignore +++ b/.gitignore @@ -38,10 +38,10 @@ playground.xcworkspace # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # -# Pods/ +Pods/ # # Add this line if you want to avoid checking in source code from the Xcode workspace -# *.xcworkspace +*.xcworkspace # Carthage # diff --git a/Fableon.xcodeproj/project.pbxproj b/Fableon.xcodeproj/project.pbxproj new file mode 100644 index 0000000..847eadf --- /dev/null +++ b/Fableon.xcodeproj/project.pbxproj @@ -0,0 +1,1234 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + B8B1DA3824F2148CEEF9F162 /* Pods_Fableon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4958FEE55B4555A94F11F00 /* Pods_Fableon.framework */; }; + F301F6472E974B6300E76A90 /* FARecommendPlayerControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F301F6462E974B6300E76A90 /* FARecommendPlayerControlView.swift */; }; + F37103312E978F8C00E7F171 /* FACollectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103302E978F8C00E7F171 /* FACollectViewController.swift */; }; + F37103352E97929F00E7F171 /* FACollectCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F37103342E97929F00E7F171 /* FACollectCell.xib */; }; + F37103362E97929F00E7F171 /* FACollectCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103332E97929F00E7F171 /* FACollectCell.swift */; }; + F371033F2E97BDF800E7F171 /* FAHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F371033E2E97BDF800E7F171 /* FAHistoryViewController.swift */; }; + F37103412E97C20C00E7F171 /* UINavigationBar+FAAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103402E97C20500E7F171 /* UINavigationBar+FAAdd.swift */; }; + F37103462E9CF9EE00E7F171 /* FAHistoryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F37103452E9CF9EE00E7F171 /* FAHistoryCell.xib */; }; + F37103472E9CF9EE00E7F171 /* FAHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103442E9CF9EE00E7F171 /* FAHistoryCell.swift */; }; + F37103492E9DD98600E7F171 /* UIScrollView+FARefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103482E9DD97200E7F171 /* UIScrollView+FARefresh.swift */; }; + F371034D2E9DF9FB00E7F171 /* FASearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F371034C2E9DF9FB00E7F171 /* FASearchViewController.swift */; }; + F371034F2E9DFB2000E7F171 /* FASearchInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F371034E2E9DFB2000E7F171 /* FASearchInputView.swift */; }; + F37103512E9E1D7800E7F171 /* FASearchHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103502E9E1D7800E7F171 /* FASearchHomeView.swift */; }; + F37103532E9E1E9300E7F171 /* FASearchRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103522E9E1E9300E7F171 /* FASearchRecordView.swift */; }; + F37103562E9E1FA500E7F171 /* FASearchRecordCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103542E9E1FA500E7F171 /* FASearchRecordCell.swift */; }; + F37103572E9E1FA500E7F171 /* FASearchRecordCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F37103552E9E1FA500E7F171 /* FASearchRecordCell.xib */; }; + F37103592E9E281C00E7F171 /* FASearchRecommendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103582E9E281C00E7F171 /* FASearchRecommendView.swift */; }; + F371035E2E9E2E7400E7F171 /* FASearchRecommendCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F371035D2E9E2E7400E7F171 /* FASearchRecommendCell.xib */; }; + F371035F2E9E2E7400E7F171 /* FASearchRecommendCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F371035C2E9E2E7400E7F171 /* FASearchRecommendCell.swift */; }; + F37103612E9E379E00E7F171 /* FASearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103602E9E379E00E7F171 /* FASearchResultView.swift */; }; + F37103662E9E3ABC00E7F171 /* FASearchResultCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F37103652E9E3ABC00E7F171 /* FASearchResultCell.xib */; }; + F37103672E9E3ABC00E7F171 /* FASearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103642E9E3ABC00E7F171 /* FASearchResultCell.swift */; }; + F37103692E9E44A000E7F171 /* FASearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103682E9E44A000E7F171 /* FASearchViewModel.swift */; }; + F371036B2E9E530400E7F171 /* UIStackView+FAAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F371036A2E9E52FE00E7F171 /* UIStackView+FAAdd.swift */; }; + F371036E2E9E6E7800E7F171 /* FAEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = F371036D2E9E6E6F00E7F171 /* FAEmpty.swift */; }; + F37103712E9F964000E7F171 /* FALocalized.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103702E9F963700E7F171 /* FALocalized.swift */; }; + F37103732E9F9E0D00E7F171 /* FAAboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103722E9F9E0D00E7F171 /* FAAboutViewController.swift */; }; + F37103762E9FA15B00E7F171 /* FAAboutCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103742E9FA15B00E7F171 /* FAAboutCell.swift */; }; + F37103772E9FA15B00E7F171 /* FAAboutCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F37103752E9FA15B00E7F171 /* FAAboutCell.xib */; }; + F37103792E9FA91C00E7F171 /* FAAboutHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103782E9FA91C00E7F171 /* FAAboutHeaderView.swift */; }; + F371037B2EA0820C00E7F171 /* FASettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F371037A2EA0820C00E7F171 /* FASettingViewController.swift */; }; + F371037E2EA082CD00E7F171 /* FAWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F371037D2EA082C700E7F171 /* FAWebView.swift */; }; + F37103802EA0839500E7F171 /* FABaseWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F371037F2EA0839500E7F171 /* FABaseWebViewController.swift */; }; + F37103822EA0868100E7F171 /* FAAppWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103812EA0868100E7F171 /* FAAppWebViewController.swift */; }; + F37103842EA0873B00E7F171 /* Dictionary+FAAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103832EA0873400E7F171 /* Dictionary+FAAdd.swift */; }; + F37103862EA087FB00E7F171 /* FAFeedbackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103852EA087FB00E7F171 /* FAFeedbackViewController.swift */; }; + F37103882EA08B6F00E7F171 /* FANetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F37103872EA08B6F00E7F171 /* FANetworkMonitor.swift */; }; + F38C25FA2E86A217008C22C3 /* UIView+FAAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F38C25F92E86A20F008C22C3 /* UIView+FAAdd.swift */; }; + F38C25FD2E86A290008C22C3 /* FADefine.swift in Sources */ = {isa = PBXBuildFile; fileRef = F38C25FC2E86A290008C22C3 /* FADefine.swift */; }; + F38C25FF2E86B663008C22C3 /* AppDelegate+FAConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = F38C25FE2E86B65A008C22C3 /* AppDelegate+FAConfig.swift */; }; + F39E66372E77BAD0008AAAFA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39E662D2E77BAD0008AAAFA /* AppDelegate.swift */; }; + F39E66382E77BAD0008AAAFA /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39E66342E77BAD0008AAAFA /* SceneDelegate.swift */; }; + F39E663A2E77BAD0008AAAFA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F39E662E2E77BAD0008AAAFA /* Assets.xcassets */; }; + F39E663C2E77BAD0008AAAFA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F39E66312E77BAD0008AAAFA /* LaunchScreen.storyboard */; }; + F3A792AD2E77DF830097E0BC /* FAUserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792AC2E77DF830097E0BC /* FAUserInfo.swift */; }; + F3A792AF2E77DFB90097E0BC /* FATokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792AE2E77DFB70097E0BC /* FATokenModel.swift */; }; + F3A792B12E77DFEE0097E0BC /* FALogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792B02E77DFE90097E0BC /* FALogin.swift */; }; + F3A792B42E77E03C0097E0BC /* UserDefaults+FAAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792B32E77E0360097E0BC /* UserDefaults+FAAdd.swift */; }; + F3A792B62E77E0CC0097E0BC /* FANetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792B52E77E0CA0097E0BC /* FANetworkManager.swift */; }; + F3A792B82E77E0EF0097E0BC /* FACryptorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792B72E77E0E50097E0BC /* FACryptorService.swift */; }; + F3A792BA2E77E1210097E0BC /* FAAPIPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792B92E77E1190097E0BC /* FAAPIPath.swift */; }; + F3A792BC2E77E2EE0097E0BC /* String+FAAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792BB2E77E2ED0097E0BC /* String+FAAdd.swift */; }; + F3A792BF2E77E4B80097E0BC /* FATool.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792BE2E77E4B70097E0BC /* FATool.swift */; }; + F3A792C12E77E6BA0097E0BC /* UIScreen+FAAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792C02E77E6B90097E0BC /* UIScreen+FAAdd.swift */; }; + F3A792C42E77E8070097E0BC /* FATabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792C32E77E8070097E0BC /* FATabBarController.swift */; }; + F3A792C62E77E93E0097E0BC /* FANavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792C52E77E93E0097E0BC /* FANavigationController.swift */; }; + F3A792C82E77EED10097E0BC /* Font+FAAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792C72E77EEC80097E0BC /* Font+FAAdd.swift */; }; + F3A792D62E77F70C0097E0BC /* FAShortPlayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792CA2E77F70C0097E0BC /* FAShortPlayModel.swift */; }; + F3A792D82E77F70C0097E0BC /* FAShortDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792D32E77F70C0097E0BC /* FAShortDetailViewModel.swift */; }; + F3A792D92E77F70C0097E0BC /* FAShortDetailModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792C92E77F70C0097E0BC /* FAShortDetailModel.swift */; }; + F3A792DA2E77F70C0097E0BC /* FAPlayerDetailCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792CF2E77F70C0097E0BC /* FAPlayerDetailCell.swift */; }; + F3A792DB2E77F70C0097E0BC /* FAVideoInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792CB2E77F70C0097E0BC /* FAVideoInfoModel.swift */; }; + F3A792DC2E77F70C0097E0BC /* FAPlayerDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792D12E77F70C0097E0BC /* FAPlayerDetailViewController.swift */; }; + F3A792DF2E77F7EA0097E0BC /* FAAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792DE2E77F7E80097E0BC /* FAAPI.swift */; }; + F3A792E82E77F8590097E0BC /* FAHomeModuleItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792E72E77F8580097E0BC /* FAHomeModuleItem.swift */; }; + F3A792EA2E77F8820097E0BC /* FAHomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792E92E77F8820097E0BC /* FAHomeViewModel.swift */; }; + F3A792F22E77F8A80097E0BC /* FAHomeMustSeeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792EE2E77F8A80097E0BC /* FAHomeMustSeeView.swift */; }; + F3A792F42E77F8A80097E0BC /* FAHomeMustSeeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792EC2E77F8A80097E0BC /* FAHomeMustSeeContentView.swift */; }; + F3A792F52E77F8A80097E0BC /* FAHomeRecommendedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792F02E77F8A80097E0BC /* FAHomeRecommendedItemView.swift */; }; + F3A792F72E77F8A80097E0BC /* FAHomeNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792EF2E77F8A80097E0BC /* FAHomeNewView.swift */; }; + F3A792F82E77F8A80097E0BC /* FAHomeMustSeeShortView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792ED2E77F8A80097E0BC /* FAHomeMustSeeShortView.swift */; }; + F3A792FD2E77F97C0097E0BC /* FSPagerSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792FC2E77F97B0097E0BC /* FSPagerSwiftUIView.swift */; }; + F3A793002E77FA0C0097E0BC /* SwiftUIExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A792FF2E77FA0B0097E0BC /* SwiftUIExtension.swift */; }; + F3A793022E77FAFB0097E0BC /* FAViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A793012E77FAFB0097E0BC /* FAViewController.swift */; }; + F3A793042E77FB110097E0BC /* FAHomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A793032E77FB110097E0BC /* FAHomeViewController.swift */; }; + F3A793092E7812F60097E0BC /* FAWaterfallFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A793082E7812F00097E0BC /* FAWaterfallFlowLayout.swift */; }; + F3A7930C2E7813FE0097E0BC /* FACollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A7930B2E7813FE0097E0BC /* FACollectionView.swift */; }; + F3A793122E78F8970097E0BC /* FAHomeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A793112E78F8970097E0BC /* FAHomeItem.swift */; }; + F3A793142E78FC4C0097E0BC /* FAHomeBannerContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A793132E78FC4C0097E0BC /* FAHomeBannerContentCell.swift */; }; + F3A793162E790CC90097E0BC /* FAHomeBannerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A793152E790CC90097E0BC /* FAHomeBannerCell.swift */; }; + F3A793182E790D440097E0BC /* FAImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A793172E790D440097E0BC /* FAImageView.swift */; }; + F3A7931A2E7911420097E0BC /* FAPagerViewTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A793192E7911420097E0BC /* FAPagerViewTransformer.swift */; }; + F3A7931C2E792D0D0097E0BC /* FAHomeMustSeeContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A7931B2E792D0D0097E0BC /* FAHomeMustSeeContentCell.swift */; }; + F3A7931E2E793D000097E0BC /* FAHomeNewContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A7931D2E793D000097E0BC /* FAHomeNewContentCell.swift */; }; + F3A793232E7944FF0097E0BC /* FAHomeRecommendedCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3A793222E7944FF0097E0BC /* FAHomeRecommendedCell.xib */; }; + F3A793242E7944FF0097E0BC /* FAHomeRecommendedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A793212E7944FF0097E0BC /* FAHomeRecommendedCell.swift */; }; + F3A793292E795B4C0097E0BC /* FAHomeSectionTitleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3A793282E795B4C0097E0BC /* FAHomeSectionTitleView.xib */; }; + F3A7932A2E795B4C0097E0BC /* FAHomeSectionTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A793272E795B4C0097E0BC /* FAHomeSectionTitleView.swift */; }; + F3A7932C2E796EF80097E0BC /* FAPlayerDetailControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A7932B2E796EF80097E0BC /* FAPlayerDetailControlView.swift */; }; + F3A793532E7BA54A0097E0BC /* FAPlayerEpUIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A793522E7BA54A0097E0BC /* FAPlayerEpUIButton.swift */; }; + F3A798B72E828C180097E0BC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F3A798B52E828C180097E0BC /* Localizable.strings */; }; + F3A798B92E828F5E0097E0BC /* FAPlayerProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A798B82E828F5E0097E0BC /* FAPlayerProgressView.swift */; }; + F3A798BC2E82AB6F0097E0BC /* FAEpSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A798BB2E82AB6F0097E0BC /* FAEpSelectorView.swift */; }; + F3A798BE2E82ACD10097E0BC /* FAPanModalContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3A798BD2E82ACD10097E0BC /* FAPanModalContentView.swift */; }; + F3C9AE5B2E77DDE100E25109 /* FAKeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C9AE5A2E77DDDF00E25109 /* FAKeychainHelper.swift */; }; + F3C9AE5D2E77DED000E25109 /* FADeviceIDManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3C9AE5C2E77DECF00E25109 /* FADeviceIDManager.swift */; }; + F3DCC0452E89530200D58007 /* CGMutablePath+FARoundedCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC0442E8952FA00D58007 /* CGMutablePath+FARoundedCorner.swift */; }; + F3DCC0482E8A65B000D58007 /* FAEpSelectorCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3DCC0472E8A65B000D58007 /* FAEpSelectorCell.xib */; }; + F3DCC0492E8A65B000D58007 /* FAEpSelectorCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC0462E8A65B000D58007 /* FAEpSelectorCell.swift */; }; + F3DCC04B2E8A6EAE00D58007 /* FAEpMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC04A2E8A6EAE00D58007 /* FAEpMenuView.swift */; }; + F3DCC04D2E8A6F0D00D58007 /* FAScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC04C2E8A6F0D00D58007 /* FAScrollView.swift */; }; + F3DCC0502E8A861300D58007 /* FAHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC04F2E8A860D00D58007 /* FAHUD.swift */; }; + F3DCC0522E8A863D00D58007 /* FAToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC0512E8A863C00D58007 /* FAToast.swift */; }; + F3DCC0572E8A8EE800D58007 /* FAMeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC0562E8A8EE800D58007 /* FAMeViewController.swift */; }; + F3DCC05A2E8A931900D58007 /* FAMeHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC0592E8A931900D58007 /* FAMeHeaderView.swift */; }; + F3DCC05C2E8A9C1B00D58007 /* FATableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC05B2E8A9C1B00D58007 /* FATableView.swift */; }; + F3DCC05E2E8A9C5800D58007 /* FATableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC05D2E8A9C5800D58007 /* FATableViewCell.swift */; }; + F3DCC0632E8A9E7600D58007 /* FAMeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC0612E8A9E7600D58007 /* FAMeCell.swift */; }; + F3DCC0642E8A9E7600D58007 /* FAMeCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F3DCC0622E8A9E7600D58007 /* FAMeCell.xib */; }; + F3DCC0672E8AA14A00D58007 /* FAMeItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC0662E8AA14A00D58007 /* FAMeItemModel.swift */; }; + F3DCC08B2E8BB16F00D58007 /* FARecommendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC08A2E8BB16F00D58007 /* FARecommendViewController.swift */; }; + F3DCC08E2E8BB1F100D58007 /* FARecommendViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC08D2E8BB1F100D58007 /* FARecommendViewModel.swift */; }; + F3DCC0912E8BBB7600D58007 /* FARecommendPlayerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3DCC0902E8BBB7600D58007 /* FARecommendPlayerCell.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 196896C43C7C7895E473B9EB /* Pods-Fableon.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Fableon.debug.xcconfig"; path = "Target Support Files/Pods-Fableon/Pods-Fableon.debug.xcconfig"; sourceTree = ""; }; + C4958FEE55B4555A94F11F00 /* Pods_Fableon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Fableon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DC14083E24B746ED3DE2FE0C /* Pods-Fableon.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Fableon.release.xcconfig"; path = "Target Support Files/Pods-Fableon/Pods-Fableon.release.xcconfig"; sourceTree = ""; }; + F301F6462E974B6300E76A90 /* FARecommendPlayerControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FARecommendPlayerControlView.swift; sourceTree = ""; }; + F37103302E978F8C00E7F171 /* FACollectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FACollectViewController.swift; sourceTree = ""; }; + F37103332E97929F00E7F171 /* FACollectCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FACollectCell.swift; sourceTree = ""; }; + F37103342E97929F00E7F171 /* FACollectCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FACollectCell.xib; sourceTree = ""; }; + F37103372E97A09500E7F171 /* Fableon-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Fableon-Bridging-Header.h"; sourceTree = ""; }; + F371033E2E97BDF800E7F171 /* FAHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHistoryViewController.swift; sourceTree = ""; }; + F37103402E97C20500E7F171 /* UINavigationBar+FAAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+FAAdd.swift"; sourceTree = ""; }; + F37103442E9CF9EE00E7F171 /* FAHistoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHistoryCell.swift; sourceTree = ""; }; + F37103452E9CF9EE00E7F171 /* FAHistoryCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FAHistoryCell.xib; sourceTree = ""; }; + F37103482E9DD97200E7F171 /* UIScrollView+FARefresh.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+FARefresh.swift"; sourceTree = ""; }; + F371034C2E9DF9FB00E7F171 /* FASearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FASearchViewController.swift; sourceTree = ""; }; + F371034E2E9DFB2000E7F171 /* FASearchInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FASearchInputView.swift; sourceTree = ""; }; + F37103502E9E1D7800E7F171 /* FASearchHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FASearchHomeView.swift; sourceTree = ""; }; + F37103522E9E1E9300E7F171 /* FASearchRecordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FASearchRecordView.swift; sourceTree = ""; }; + F37103542E9E1FA500E7F171 /* FASearchRecordCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FASearchRecordCell.swift; sourceTree = ""; }; + F37103552E9E1FA500E7F171 /* FASearchRecordCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FASearchRecordCell.xib; sourceTree = ""; }; + F37103582E9E281C00E7F171 /* FASearchRecommendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FASearchRecommendView.swift; sourceTree = ""; }; + F371035C2E9E2E7400E7F171 /* FASearchRecommendCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FASearchRecommendCell.swift; sourceTree = ""; }; + F371035D2E9E2E7400E7F171 /* FASearchRecommendCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FASearchRecommendCell.xib; sourceTree = ""; }; + F37103602E9E379E00E7F171 /* FASearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FASearchResultView.swift; sourceTree = ""; }; + F37103642E9E3ABC00E7F171 /* FASearchResultCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FASearchResultCell.swift; sourceTree = ""; }; + F37103652E9E3ABC00E7F171 /* FASearchResultCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FASearchResultCell.xib; sourceTree = ""; }; + F37103682E9E44A000E7F171 /* FASearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FASearchViewModel.swift; sourceTree = ""; }; + F371036A2E9E52FE00E7F171 /* UIStackView+FAAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+FAAdd.swift"; sourceTree = ""; }; + F371036D2E9E6E6F00E7F171 /* FAEmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAEmpty.swift; sourceTree = ""; }; + F37103702E9F963700E7F171 /* FALocalized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FALocalized.swift; sourceTree = ""; }; + F37103722E9F9E0D00E7F171 /* FAAboutViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAAboutViewController.swift; sourceTree = ""; }; + F37103742E9FA15B00E7F171 /* FAAboutCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAAboutCell.swift; sourceTree = ""; }; + F37103752E9FA15B00E7F171 /* FAAboutCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FAAboutCell.xib; sourceTree = ""; }; + F37103782E9FA91C00E7F171 /* FAAboutHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAAboutHeaderView.swift; sourceTree = ""; }; + F371037A2EA0820C00E7F171 /* FASettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FASettingViewController.swift; sourceTree = ""; }; + F371037D2EA082C700E7F171 /* FAWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAWebView.swift; sourceTree = ""; }; + F371037F2EA0839500E7F171 /* FABaseWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FABaseWebViewController.swift; sourceTree = ""; }; + F37103812EA0868100E7F171 /* FAAppWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAAppWebViewController.swift; sourceTree = ""; }; + F37103832EA0873400E7F171 /* Dictionary+FAAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+FAAdd.swift"; sourceTree = ""; }; + F37103852EA087FB00E7F171 /* FAFeedbackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAFeedbackViewController.swift; sourceTree = ""; }; + F37103872EA08B6F00E7F171 /* FANetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FANetworkMonitor.swift; sourceTree = ""; }; + F38C25F92E86A20F008C22C3 /* UIView+FAAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+FAAdd.swift"; sourceTree = ""; }; + F38C25FC2E86A290008C22C3 /* FADefine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FADefine.swift; sourceTree = ""; }; + F38C25FE2E86B65A008C22C3 /* AppDelegate+FAConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+FAConfig.swift"; sourceTree = ""; }; + F39E65E52E77B824008AAAFA /* Fableon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Fableon.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F39E662D2E77BAD0008AAAFA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + F39E662E2E77BAD0008AAAFA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + F39E662F2E77BAD0008AAAFA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F39E66302E77BAD0008AAAFA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + F39E66342E77BAD0008AAAFA /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + F3A792AC2E77DF830097E0BC /* FAUserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAUserInfo.swift; sourceTree = ""; }; + F3A792AE2E77DFB70097E0BC /* FATokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FATokenModel.swift; sourceTree = ""; }; + F3A792B02E77DFE90097E0BC /* FALogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FALogin.swift; sourceTree = ""; }; + F3A792B32E77E0360097E0BC /* UserDefaults+FAAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+FAAdd.swift"; sourceTree = ""; }; + F3A792B52E77E0CA0097E0BC /* FANetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FANetworkManager.swift; sourceTree = ""; }; + F3A792B72E77E0E50097E0BC /* FACryptorService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FACryptorService.swift; sourceTree = ""; }; + F3A792B92E77E1190097E0BC /* FAAPIPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAAPIPath.swift; sourceTree = ""; }; + F3A792BB2E77E2ED0097E0BC /* String+FAAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+FAAdd.swift"; sourceTree = ""; }; + F3A792BE2E77E4B70097E0BC /* FATool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FATool.swift; sourceTree = ""; }; + F3A792C02E77E6B90097E0BC /* UIScreen+FAAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScreen+FAAdd.swift"; sourceTree = ""; }; + F3A792C32E77E8070097E0BC /* FATabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FATabBarController.swift; sourceTree = ""; }; + F3A792C52E77E93E0097E0BC /* FANavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FANavigationController.swift; sourceTree = ""; }; + F3A792C72E77EEC80097E0BC /* Font+FAAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+FAAdd.swift"; sourceTree = ""; }; + F3A792C92E77F70C0097E0BC /* FAShortDetailModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAShortDetailModel.swift; sourceTree = ""; }; + F3A792CA2E77F70C0097E0BC /* FAShortPlayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAShortPlayModel.swift; sourceTree = ""; }; + F3A792CB2E77F70C0097E0BC /* FAVideoInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAVideoInfoModel.swift; sourceTree = ""; }; + F3A792CF2E77F70C0097E0BC /* FAPlayerDetailCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAPlayerDetailCell.swift; sourceTree = ""; }; + F3A792D12E77F70C0097E0BC /* FAPlayerDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAPlayerDetailViewController.swift; sourceTree = ""; }; + F3A792D32E77F70C0097E0BC /* FAShortDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAShortDetailViewModel.swift; sourceTree = ""; }; + F3A792DE2E77F7E80097E0BC /* FAAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAAPI.swift; sourceTree = ""; }; + F3A792E72E77F8580097E0BC /* FAHomeModuleItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeModuleItem.swift; sourceTree = ""; }; + F3A792E92E77F8820097E0BC /* FAHomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeViewModel.swift; sourceTree = ""; }; + F3A792EC2E77F8A80097E0BC /* FAHomeMustSeeContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeMustSeeContentView.swift; sourceTree = ""; }; + F3A792ED2E77F8A80097E0BC /* FAHomeMustSeeShortView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeMustSeeShortView.swift; sourceTree = ""; }; + F3A792EE2E77F8A80097E0BC /* FAHomeMustSeeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeMustSeeView.swift; sourceTree = ""; }; + F3A792EF2E77F8A80097E0BC /* FAHomeNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeNewView.swift; sourceTree = ""; }; + F3A792F02E77F8A80097E0BC /* FAHomeRecommendedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeRecommendedItemView.swift; sourceTree = ""; }; + F3A792FC2E77F97B0097E0BC /* FSPagerSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSPagerSwiftUIView.swift; sourceTree = ""; }; + F3A792FF2E77FA0B0097E0BC /* SwiftUIExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIExtension.swift; sourceTree = ""; }; + F3A793012E77FAFB0097E0BC /* FAViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAViewController.swift; sourceTree = ""; }; + F3A793032E77FB110097E0BC /* FAHomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeViewController.swift; sourceTree = ""; }; + F3A793082E7812F00097E0BC /* FAWaterfallFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAWaterfallFlowLayout.swift; sourceTree = ""; }; + F3A7930B2E7813FE0097E0BC /* FACollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FACollectionView.swift; sourceTree = ""; }; + F3A793112E78F8970097E0BC /* FAHomeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeItem.swift; sourceTree = ""; }; + F3A793132E78FC4C0097E0BC /* FAHomeBannerContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeBannerContentCell.swift; sourceTree = ""; }; + F3A793152E790CC90097E0BC /* FAHomeBannerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeBannerCell.swift; sourceTree = ""; }; + F3A793172E790D440097E0BC /* FAImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAImageView.swift; sourceTree = ""; }; + F3A793192E7911420097E0BC /* FAPagerViewTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAPagerViewTransformer.swift; sourceTree = ""; }; + F3A7931B2E792D0D0097E0BC /* FAHomeMustSeeContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeMustSeeContentCell.swift; sourceTree = ""; }; + F3A7931D2E793D000097E0BC /* FAHomeNewContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeNewContentCell.swift; sourceTree = ""; }; + F3A793212E7944FF0097E0BC /* FAHomeRecommendedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeRecommendedCell.swift; sourceTree = ""; }; + F3A793222E7944FF0097E0BC /* FAHomeRecommendedCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FAHomeRecommendedCell.xib; sourceTree = ""; }; + F3A793272E795B4C0097E0BC /* FAHomeSectionTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHomeSectionTitleView.swift; sourceTree = ""; }; + F3A793282E795B4C0097E0BC /* FAHomeSectionTitleView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FAHomeSectionTitleView.xib; sourceTree = ""; }; + F3A7932B2E796EF80097E0BC /* FAPlayerDetailControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAPlayerDetailControlView.swift; sourceTree = ""; }; + F3A793522E7BA54A0097E0BC /* FAPlayerEpUIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAPlayerEpUIButton.swift; sourceTree = ""; }; + F3A798B62E828C180097E0BC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + F3A798B82E828F5E0097E0BC /* FAPlayerProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAPlayerProgressView.swift; sourceTree = ""; }; + F3A798BB2E82AB6F0097E0BC /* FAEpSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAEpSelectorView.swift; sourceTree = ""; }; + F3A798BD2E82ACD10097E0BC /* FAPanModalContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAPanModalContentView.swift; sourceTree = ""; }; + F3C9AE5A2E77DDDF00E25109 /* FAKeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAKeychainHelper.swift; sourceTree = ""; }; + F3C9AE5C2E77DECF00E25109 /* FADeviceIDManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FADeviceIDManager.swift; sourceTree = ""; }; + F3DCC0442E8952FA00D58007 /* CGMutablePath+FARoundedCorner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGMutablePath+FARoundedCorner.swift"; sourceTree = ""; }; + F3DCC0462E8A65B000D58007 /* FAEpSelectorCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAEpSelectorCell.swift; sourceTree = ""; }; + F3DCC0472E8A65B000D58007 /* FAEpSelectorCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FAEpSelectorCell.xib; sourceTree = ""; }; + F3DCC04A2E8A6EAE00D58007 /* FAEpMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAEpMenuView.swift; sourceTree = ""; }; + F3DCC04C2E8A6F0D00D58007 /* FAScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAScrollView.swift; sourceTree = ""; }; + F3DCC04F2E8A860D00D58007 /* FAHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAHUD.swift; sourceTree = ""; }; + F3DCC0512E8A863C00D58007 /* FAToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAToast.swift; sourceTree = ""; }; + F3DCC0562E8A8EE800D58007 /* FAMeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAMeViewController.swift; sourceTree = ""; }; + F3DCC0592E8A931900D58007 /* FAMeHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAMeHeaderView.swift; sourceTree = ""; }; + F3DCC05B2E8A9C1B00D58007 /* FATableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FATableView.swift; sourceTree = ""; }; + F3DCC05D2E8A9C5800D58007 /* FATableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FATableViewCell.swift; sourceTree = ""; }; + F3DCC0612E8A9E7600D58007 /* FAMeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAMeCell.swift; sourceTree = ""; }; + F3DCC0622E8A9E7600D58007 /* FAMeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FAMeCell.xib; sourceTree = ""; }; + F3DCC0662E8AA14A00D58007 /* FAMeItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAMeItemModel.swift; sourceTree = ""; }; + F3DCC08A2E8BB16F00D58007 /* FARecommendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FARecommendViewController.swift; sourceTree = ""; }; + F3DCC08D2E8BB1F100D58007 /* FARecommendViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FARecommendViewModel.swift; sourceTree = ""; }; + F3DCC0902E8BBB7600D58007 /* FARecommendPlayerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FARecommendPlayerCell.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + F39E65E22E77B824008AAAFA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B8B1DA3824F2148CEEF9F162 /* Pods_Fableon.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 61670878B802CB009A47D7FD /* Frameworks */ = { + isa = PBXGroup; + children = ( + C4958FEE55B4555A94F11F00 /* Pods_Fableon.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + A9F088F683BE0CE6B61480BB /* Pods */ = { + isa = PBXGroup; + children = ( + 196896C43C7C7895E473B9EB /* Pods-Fableon.debug.xcconfig */, + DC14083E24B746ED3DE2FE0C /* Pods-Fableon.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + F371032E2E978F4D00E7F171 /* MyShort */ = { + isa = PBXGroup; + children = ( + F371032F2E978F6600E7F171 /* C */, + F37103322E97927A00E7F171 /* V */, + ); + path = MyShort; + sourceTree = ""; + }; + F371032F2E978F6600E7F171 /* C */ = { + isa = PBXGroup; + children = ( + F37103302E978F8C00E7F171 /* FACollectViewController.swift */, + F371033E2E97BDF800E7F171 /* FAHistoryViewController.swift */, + ); + path = C; + sourceTree = ""; + }; + F37103322E97927A00E7F171 /* V */ = { + isa = PBXGroup; + children = ( + F37103332E97929F00E7F171 /* FACollectCell.swift */, + F37103342E97929F00E7F171 /* FACollectCell.xib */, + F37103442E9CF9EE00E7F171 /* FAHistoryCell.swift */, + F37103452E9CF9EE00E7F171 /* FAHistoryCell.xib */, + ); + path = V; + sourceTree = ""; + }; + F371036C2E9E6E3300E7F171 /* Empty */ = { + isa = PBXGroup; + children = ( + F371036D2E9E6E6F00E7F171 /* FAEmpty.swift */, + ); + path = Empty; + sourceTree = ""; + }; + F371036F2E9F960F00E7F171 /* FALocalized */ = { + isa = PBXGroup; + children = ( + F37103702E9F963700E7F171 /* FALocalized.swift */, + ); + path = FALocalized; + sourceTree = ""; + }; + F371037C2EA0829D00E7F171 /* WebView */ = { + isa = PBXGroup; + children = ( + F371037D2EA082C700E7F171 /* FAWebView.swift */, + F371037F2EA0839500E7F171 /* FABaseWebViewController.swift */, + F37103812EA0868100E7F171 /* FAAppWebViewController.swift */, + ); + path = WebView; + sourceTree = ""; + }; + F38C25FB2E86A265008C22C3 /* Define */ = { + isa = PBXGroup; + children = ( + F38C25FC2E86A290008C22C3 /* FADefine.swift */, + ); + path = Define; + sourceTree = ""; + }; + F39E65DC2E77B824008AAAFA = { + isa = PBXGroup; + children = ( + F39E66362E77BAD0008AAAFA /* Fableon */, + F39E65E62E77B824008AAAFA /* Products */, + A9F088F683BE0CE6B61480BB /* Pods */, + 61670878B802CB009A47D7FD /* Frameworks */, + ); + sourceTree = ""; + }; + F39E65E62E77B824008AAAFA /* Products */ = { + isa = PBXGroup; + children = ( + F39E65E52E77B824008AAAFA /* Fableon.app */, + ); + name = Products; + sourceTree = ""; + }; + F39E66362E77BAD0008AAAFA /* Fableon */ = { + isa = PBXGroup; + children = ( + F39E663F2E77BAF1008AAAFA /* App */, + F39E663E2E77BADC008AAAFA /* Base */, + F3C9AE582E77DD8C00E25109 /* Class */, + F39E66402E77BBF9008AAAFA /* Source */, + F3C9AE572E77DD8400E25109 /* Libs */, + ); + path = Fableon; + sourceTree = ""; + }; + F39E663E2E77BADC008AAAFA /* Base */ = { + isa = PBXGroup; + children = ( + F371037C2EA0829D00E7F171 /* WebView */, + F38C25FB2E86A265008C22C3 /* Define */, + F3A792C22E77E7D50097E0BC /* Controller */, + F3A7930A2E7813DA0097E0BC /* View */, + F3A792B22E77E0290097E0BC /* Extension */, + F3C9AE562E77C1CE00E25109 /* Request */, + ); + path = Base; + sourceTree = ""; + }; + F39E663F2E77BAF1008AAAFA /* App */ = { + isa = PBXGroup; + children = ( + F39E662D2E77BAD0008AAAFA /* AppDelegate.swift */, + F39E66342E77BAD0008AAAFA /* SceneDelegate.swift */, + F38C25FE2E86B65A008C22C3 /* AppDelegate+FAConfig.swift */, + ); + path = App; + sourceTree = ""; + }; + F39E66402E77BBF9008AAAFA /* Source */ = { + isa = PBXGroup; + children = ( + F37103372E97A09500E7F171 /* Fableon-Bridging-Header.h */, + F39E662E2E77BAD0008AAAFA /* Assets.xcassets */, + F39E662F2E77BAD0008AAAFA /* Info.plist */, + F39E66312E77BAD0008AAAFA /* LaunchScreen.storyboard */, + F3A798B52E828C180097E0BC /* Localizable.strings */, + ); + path = Source; + sourceTree = ""; + }; + F3A792B22E77E0290097E0BC /* Extension */ = { + isa = PBXGroup; + children = ( + F3A792FE2E77F9FB0097E0BC /* UI */, + F3A792C72E77EEC80097E0BC /* Font+FAAdd.swift */, + F3A792C02E77E6B90097E0BC /* UIScreen+FAAdd.swift */, + F3A792BB2E77E2ED0097E0BC /* String+FAAdd.swift */, + F3A792B32E77E0360097E0BC /* UserDefaults+FAAdd.swift */, + F38C25F92E86A20F008C22C3 /* UIView+FAAdd.swift */, + F3DCC0442E8952FA00D58007 /* CGMutablePath+FARoundedCorner.swift */, + F37103402E97C20500E7F171 /* UINavigationBar+FAAdd.swift */, + F37103482E9DD97200E7F171 /* UIScrollView+FARefresh.swift */, + F371036A2E9E52FE00E7F171 /* UIStackView+FAAdd.swift */, + F37103832EA0873400E7F171 /* Dictionary+FAAdd.swift */, + ); + path = Extension; + sourceTree = ""; + }; + F3A792BD2E77E4AF0097E0BC /* FATool */ = { + isa = PBXGroup; + children = ( + F3A792BE2E77E4B70097E0BC /* FATool.swift */, + ); + path = FATool; + sourceTree = ""; + }; + F3A792C22E77E7D50097E0BC /* Controller */ = { + isa = PBXGroup; + children = ( + F3A792C32E77E8070097E0BC /* FATabBarController.swift */, + F3A792C52E77E93E0097E0BC /* FANavigationController.swift */, + F3A793012E77FAFB0097E0BC /* FAViewController.swift */, + ); + path = Controller; + sourceTree = ""; + }; + F3A792CC2E77F70C0097E0BC /* M */ = { + isa = PBXGroup; + children = ( + F3A792C92E77F70C0097E0BC /* FAShortDetailModel.swift */, + F3A792CA2E77F70C0097E0BC /* FAShortPlayModel.swift */, + F3A792CB2E77F70C0097E0BC /* FAVideoInfoModel.swift */, + ); + path = M; + sourceTree = ""; + }; + F3A792CE2E77F70C0097E0BC /* UI */ = { + isa = PBXGroup; + children = ( + F3A793522E7BA54A0097E0BC /* FAPlayerEpUIButton.swift */, + ); + path = UI; + sourceTree = ""; + }; + F3A792D02E77F70C0097E0BC /* V */ = { + isa = PBXGroup; + children = ( + F3A792CF2E77F70C0097E0BC /* FAPlayerDetailCell.swift */, + F3A7932B2E796EF80097E0BC /* FAPlayerDetailControlView.swift */, + F3A798B82E828F5E0097E0BC /* FAPlayerProgressView.swift */, + F3A798BB2E82AB6F0097E0BC /* FAEpSelectorView.swift */, + F3DCC0462E8A65B000D58007 /* FAEpSelectorCell.swift */, + F3DCC0472E8A65B000D58007 /* FAEpSelectorCell.xib */, + F3DCC04A2E8A6EAE00D58007 /* FAEpMenuView.swift */, + ); + path = V; + sourceTree = ""; + }; + F3A792D22E77F70C0097E0BC /* VC */ = { + isa = PBXGroup; + children = ( + F3A792D12E77F70C0097E0BC /* FAPlayerDetailViewController.swift */, + ); + path = VC; + sourceTree = ""; + }; + F3A792D42E77F70C0097E0BC /* VM */ = { + isa = PBXGroup; + children = ( + F3A792D32E77F70C0097E0BC /* FAShortDetailViewModel.swift */, + ); + path = VM; + sourceTree = ""; + }; + F3A792D52E77F70C0097E0BC /* Player */ = { + isa = PBXGroup; + children = ( + F3A792CE2E77F70C0097E0BC /* UI */, + F3A792D22E77F70C0097E0BC /* VC */, + F3A792D02E77F70C0097E0BC /* V */, + F3A792CC2E77F70C0097E0BC /* M */, + F3A792D42E77F70C0097E0BC /* VM */, + ); + path = Player; + sourceTree = ""; + }; + F3A792DD2E77F7D20097E0BC /* FAAPI */ = { + isa = PBXGroup; + children = ( + F3A792DE2E77F7E80097E0BC /* FAAPI.swift */, + ); + path = FAAPI; + sourceTree = ""; + }; + F3A792E02E77F8040097E0BC /* Home */ = { + isa = PBXGroup; + children = ( + F3A792E52E77F8400097E0BC /* UI */, + F3A792E42E77F83A0097E0BC /* C */, + F3A792E32E77F8350097E0BC /* V */, + F3A792E22E77F82D0097E0BC /* M */, + F3A792E12E77F8220097E0BC /* VM */, + ); + path = Home; + sourceTree = ""; + }; + F3A792E12E77F8220097E0BC /* VM */ = { + isa = PBXGroup; + children = ( + F3A792E92E77F8820097E0BC /* FAHomeViewModel.swift */, + F37103682E9E44A000E7F171 /* FASearchViewModel.swift */, + ); + path = VM; + sourceTree = ""; + }; + F3A792E22E77F82D0097E0BC /* M */ = { + isa = PBXGroup; + children = ( + F3A792E72E77F8580097E0BC /* FAHomeModuleItem.swift */, + F3A793112E78F8970097E0BC /* FAHomeItem.swift */, + ); + path = M; + sourceTree = ""; + }; + F3A792E32E77F8350097E0BC /* V */ = { + isa = PBXGroup; + children = ( + F3A793132E78FC4C0097E0BC /* FAHomeBannerContentCell.swift */, + F3A793152E790CC90097E0BC /* FAHomeBannerCell.swift */, + F3A7931B2E792D0D0097E0BC /* FAHomeMustSeeContentCell.swift */, + F3A7931D2E793D000097E0BC /* FAHomeNewContentCell.swift */, + F3A793212E7944FF0097E0BC /* FAHomeRecommendedCell.swift */, + F3A793222E7944FF0097E0BC /* FAHomeRecommendedCell.xib */, + F3A793272E795B4C0097E0BC /* FAHomeSectionTitleView.swift */, + F3A793282E795B4C0097E0BC /* FAHomeSectionTitleView.xib */, + F371034E2E9DFB2000E7F171 /* FASearchInputView.swift */, + F37103502E9E1D7800E7F171 /* FASearchHomeView.swift */, + F37103602E9E379E00E7F171 /* FASearchResultView.swift */, + F37103522E9E1E9300E7F171 /* FASearchRecordView.swift */, + F37103542E9E1FA500E7F171 /* FASearchRecordCell.swift */, + F37103552E9E1FA500E7F171 /* FASearchRecordCell.xib */, + F37103582E9E281C00E7F171 /* FASearchRecommendView.swift */, + F371035C2E9E2E7400E7F171 /* FASearchRecommendCell.swift */, + F371035D2E9E2E7400E7F171 /* FASearchRecommendCell.xib */, + F37103642E9E3ABC00E7F171 /* FASearchResultCell.swift */, + F37103652E9E3ABC00E7F171 /* FASearchResultCell.xib */, + ); + path = V; + sourceTree = ""; + }; + F3A792E42E77F83A0097E0BC /* C */ = { + isa = PBXGroup; + children = ( + F3A793032E77FB110097E0BC /* FAHomeViewController.swift */, + F371034C2E9DF9FB00E7F171 /* FASearchViewController.swift */, + ); + path = C; + sourceTree = ""; + }; + F3A792E52E77F8400097E0BC /* UI */ = { + isa = PBXGroup; + children = ( + F3A792EC2E77F8A80097E0BC /* FAHomeMustSeeContentView.swift */, + F3A792ED2E77F8A80097E0BC /* FAHomeMustSeeShortView.swift */, + F3A792EE2E77F8A80097E0BC /* FAHomeMustSeeView.swift */, + F3A792EF2E77F8A80097E0BC /* FAHomeNewView.swift */, + F3A792F02E77F8A80097E0BC /* FAHomeRecommendedItemView.swift */, + ); + path = UI; + sourceTree = ""; + }; + F3A792FB2E77F9680097E0BC /* FSPagerView */ = { + isa = PBXGroup; + children = ( + F3A792FC2E77F97B0097E0BC /* FSPagerSwiftUIView.swift */, + F3A793192E7911420097E0BC /* FAPagerViewTransformer.swift */, + ); + path = FSPagerView; + sourceTree = ""; + }; + F3A792FE2E77F9FB0097E0BC /* UI */ = { + isa = PBXGroup; + children = ( + F3A792FF2E77FA0B0097E0BC /* SwiftUIExtension.swift */, + ); + path = UI; + sourceTree = ""; + }; + F3A793072E7812DE0097E0BC /* WaterfallFlowLayout */ = { + isa = PBXGroup; + children = ( + F3A793082E7812F00097E0BC /* FAWaterfallFlowLayout.swift */, + ); + path = WaterfallFlowLayout; + sourceTree = ""; + }; + F3A7930A2E7813DA0097E0BC /* View */ = { + isa = PBXGroup; + children = ( + F3DCC05B2E8A9C1B00D58007 /* FATableView.swift */, + F3DCC05D2E8A9C5800D58007 /* FATableViewCell.swift */, + F3A7930B2E7813FE0097E0BC /* FACollectionView.swift */, + F3A793172E790D440097E0BC /* FAImageView.swift */, + F3A798BD2E82ACD10097E0BC /* FAPanModalContentView.swift */, + F3DCC04C2E8A6F0D00D58007 /* FAScrollView.swift */, + ); + path = View; + sourceTree = ""; + }; + F3C9AE562E77C1CE00E25109 /* Request */ = { + isa = PBXGroup; + children = ( + F3A792DD2E77F7D20097E0BC /* FAAPI */, + F3A792B92E77E1190097E0BC /* FAAPIPath.swift */, + F3A792B72E77E0E50097E0BC /* FACryptorService.swift */, + F3A792B52E77E0CA0097E0BC /* FANetworkManager.swift */, + F37103872EA08B6F00E7F171 /* FANetworkMonitor.swift */, + ); + path = Request; + sourceTree = ""; + }; + F3C9AE572E77DD8400E25109 /* Libs */ = { + isa = PBXGroup; + children = ( + F371036F2E9F960F00E7F171 /* FALocalized */, + F371036C2E9E6E3300E7F171 /* Empty */, + F3DCC04E2E8A85F400D58007 /* HUD */, + F3A793072E7812DE0097E0BC /* WaterfallFlowLayout */, + F3A792FB2E77F9680097E0BC /* FSPagerView */, + F3A792BD2E77E4AF0097E0BC /* FATool */, + F3C9AE5E2E77DF1800E25109 /* FALogin */, + F3C9AE592E77DDB500E25109 /* FADeviceId */, + ); + path = Libs; + sourceTree = ""; + }; + F3C9AE582E77DD8C00E25109 /* Class */ = { + isa = PBXGroup; + children = ( + F3A792E02E77F8040097E0BC /* Home */, + F3DCC0532E8A8EB200D58007 /* Me */, + F3A792D52E77F70C0097E0BC /* Player */, + F3DCC0882E8BB12600D58007 /* Recommend */, + F371032E2E978F4D00E7F171 /* MyShort */, + ); + path = Class; + sourceTree = ""; + }; + F3C9AE592E77DDB500E25109 /* FADeviceId */ = { + isa = PBXGroup; + children = ( + F3C9AE5C2E77DECF00E25109 /* FADeviceIDManager.swift */, + F3C9AE5A2E77DDDF00E25109 /* FAKeychainHelper.swift */, + ); + path = FADeviceId; + sourceTree = ""; + }; + F3C9AE5E2E77DF1800E25109 /* FALogin */ = { + isa = PBXGroup; + children = ( + F3A792B02E77DFE90097E0BC /* FALogin.swift */, + F3A792AC2E77DF830097E0BC /* FAUserInfo.swift */, + F3A792AE2E77DFB70097E0BC /* FATokenModel.swift */, + ); + path = FALogin; + sourceTree = ""; + }; + F3DCC04E2E8A85F400D58007 /* HUD */ = { + isa = PBXGroup; + children = ( + F3DCC0512E8A863C00D58007 /* FAToast.swift */, + F3DCC04F2E8A860D00D58007 /* FAHUD.swift */, + ); + path = HUD; + sourceTree = ""; + }; + F3DCC0532E8A8EB200D58007 /* Me */ = { + isa = PBXGroup; + children = ( + F3DCC0552E8A8EC100D58007 /* C */, + F3DCC0582E8A92FD00D58007 /* V */, + F3DCC0652E8AA12800D58007 /* M */, + ); + path = Me; + sourceTree = ""; + }; + F3DCC0552E8A8EC100D58007 /* C */ = { + isa = PBXGroup; + children = ( + F3DCC0562E8A8EE800D58007 /* FAMeViewController.swift */, + F37103722E9F9E0D00E7F171 /* FAAboutViewController.swift */, + F371037A2EA0820C00E7F171 /* FASettingViewController.swift */, + F37103852EA087FB00E7F171 /* FAFeedbackViewController.swift */, + ); + path = C; + sourceTree = ""; + }; + F3DCC0582E8A92FD00D58007 /* V */ = { + isa = PBXGroup; + children = ( + F3DCC0592E8A931900D58007 /* FAMeHeaderView.swift */, + F3DCC0612E8A9E7600D58007 /* FAMeCell.swift */, + F3DCC0622E8A9E7600D58007 /* FAMeCell.xib */, + F37103742E9FA15B00E7F171 /* FAAboutCell.swift */, + F37103752E9FA15B00E7F171 /* FAAboutCell.xib */, + F37103782E9FA91C00E7F171 /* FAAboutHeaderView.swift */, + ); + path = V; + sourceTree = ""; + }; + F3DCC0652E8AA12800D58007 /* M */ = { + isa = PBXGroup; + children = ( + F3DCC0662E8AA14A00D58007 /* FAMeItemModel.swift */, + ); + path = M; + sourceTree = ""; + }; + F3DCC0882E8BB12600D58007 /* Recommend */ = { + isa = PBXGroup; + children = ( + F3DCC0892E8BB14E00D58007 /* C */, + F3DCC08F2E8BBB5500D58007 /* V */, + F3DCC08C2E8BB1CA00D58007 /* VM */, + ); + path = Recommend; + sourceTree = ""; + }; + F3DCC0892E8BB14E00D58007 /* C */ = { + isa = PBXGroup; + children = ( + F3DCC08A2E8BB16F00D58007 /* FARecommendViewController.swift */, + ); + path = C; + sourceTree = ""; + }; + F3DCC08C2E8BB1CA00D58007 /* VM */ = { + isa = PBXGroup; + children = ( + F3DCC08D2E8BB1F100D58007 /* FARecommendViewModel.swift */, + ); + path = VM; + sourceTree = ""; + }; + F3DCC08F2E8BBB5500D58007 /* V */ = { + isa = PBXGroup; + children = ( + F3DCC0902E8BBB7600D58007 /* FARecommendPlayerCell.swift */, + F301F6462E974B6300E76A90 /* FARecommendPlayerControlView.swift */, + ); + path = V; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + F39E65E42E77B824008AAAFA /* Fableon */ = { + isa = PBXNativeTarget; + buildConfigurationList = F39E65F82E77B826008AAAFA /* Build configuration list for PBXNativeTarget "Fableon" */; + buildPhases = ( + 9E9E59200EA11A4375AF0F90 /* [CP] Check Pods Manifest.lock */, + F39E65E12E77B824008AAAFA /* Sources */, + F39E65E22E77B824008AAAFA /* Frameworks */, + F39E65E32E77B824008AAAFA /* Resources */, + 48BC3A4ABA5C7EECD0A916D2 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Fableon; + productName = Fableon; + productReference = F39E65E52E77B824008AAAFA /* Fableon.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F39E65DD2E77B824008AAAFA /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; + TargetAttributes = { + F39E65E42E77B824008AAAFA = { + CreatedOnToolsVersion = 16.4; + LastSwiftMigration = 1640; + }; + }; + }; + buildConfigurationList = F39E65E02E77B824008AAAFA /* Build configuration list for PBXProject "Fableon" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = F39E65DC2E77B824008AAAFA; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = F39E65E62E77B824008AAAFA /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + F39E65E42E77B824008AAAFA /* Fableon */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F39E65E32E77B824008AAAFA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F3A798B72E828C180097E0BC /* Localizable.strings in Resources */, + F37103572E9E1FA500E7F171 /* FASearchRecordCell.xib in Resources */, + F39E663A2E77BAD0008AAAFA /* Assets.xcassets in Resources */, + F37103772E9FA15B00E7F171 /* FAAboutCell.xib in Resources */, + F371035E2E9E2E7400E7F171 /* FASearchRecommendCell.xib in Resources */, + F37103462E9CF9EE00E7F171 /* FAHistoryCell.xib in Resources */, + F3A793292E795B4C0097E0BC /* FAHomeSectionTitleView.xib in Resources */, + F3A793232E7944FF0097E0BC /* FAHomeRecommendedCell.xib in Resources */, + F37103662E9E3ABC00E7F171 /* FASearchResultCell.xib in Resources */, + F3DCC0482E8A65B000D58007 /* FAEpSelectorCell.xib in Resources */, + F39E663C2E77BAD0008AAAFA /* LaunchScreen.storyboard in Resources */, + F3DCC0642E8A9E7600D58007 /* FAMeCell.xib in Resources */, + F37103352E97929F00E7F171 /* FACollectCell.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 48BC3A4ABA5C7EECD0A916D2 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Fableon/Pods-Fableon-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Fableon/Pods-Fableon-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Fableon/Pods-Fableon-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9E9E59200EA11A4375AF0F90 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Fableon-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + F39E65E12E77B824008AAAFA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F3A792FD2E77F97C0097E0BC /* FSPagerSwiftUIView.swift in Sources */, + F3DCC0522E8A863D00D58007 /* FAToast.swift in Sources */, + F3A793022E77FAFB0097E0BC /* FAViewController.swift in Sources */, + F3A792B42E77E03C0097E0BC /* UserDefaults+FAAdd.swift in Sources */, + F38C25FD2E86A290008C22C3 /* FADefine.swift in Sources */, + F3A793182E790D440097E0BC /* FAImageView.swift in Sources */, + F3DCC05A2E8A931900D58007 /* FAMeHeaderView.swift in Sources */, + F3A792B62E77E0CC0097E0BC /* FANetworkManager.swift in Sources */, + F3A792C62E77E93E0097E0BC /* FANavigationController.swift in Sources */, + F37103692E9E44A000E7F171 /* FASearchViewModel.swift in Sources */, + F3A793122E78F8970097E0BC /* FAHomeItem.swift in Sources */, + F3A798BE2E82ACD10097E0BC /* FAPanModalContentView.swift in Sources */, + F3A792F22E77F8A80097E0BC /* FAHomeMustSeeView.swift in Sources */, + F3A793242E7944FF0097E0BC /* FAHomeRecommendedCell.swift in Sources */, + F3A792F42E77F8A80097E0BC /* FAHomeMustSeeContentView.swift in Sources */, + F3DCC05E2E8A9C5800D58007 /* FATableViewCell.swift in Sources */, + F371037E2EA082CD00E7F171 /* FAWebView.swift in Sources */, + F3A7931C2E792D0D0097E0BC /* FAHomeMustSeeContentCell.swift in Sources */, + F37103612E9E379E00E7F171 /* FASearchResultView.swift in Sources */, + F3A792F52E77F8A80097E0BC /* FAHomeRecommendedItemView.swift in Sources */, + F37103842EA0873B00E7F171 /* Dictionary+FAAdd.swift in Sources */, + F3A792F72E77F8A80097E0BC /* FAHomeNewView.swift in Sources */, + F3A792F82E77F8A80097E0BC /* FAHomeMustSeeShortView.swift in Sources */, + F3A793042E77FB110097E0BC /* FAHomeViewController.swift in Sources */, + F371034F2E9DFB2000E7F171 /* FASearchInputView.swift in Sources */, + F37103562E9E1FA500E7F171 /* FASearchRecordCell.swift in Sources */, + F37103792E9FA91C00E7F171 /* FAAboutHeaderView.swift in Sources */, + F3A792B12E77DFEE0097E0BC /* FALogin.swift in Sources */, + F3DCC0502E8A861300D58007 /* FAHUD.swift in Sources */, + F3DCC04D2E8A6F0D00D58007 /* FAScrollView.swift in Sources */, + F3DCC0572E8A8EE800D58007 /* FAMeViewController.swift in Sources */, + F3A792E82E77F8590097E0BC /* FAHomeModuleItem.swift in Sources */, + F3DCC0452E89530200D58007 /* CGMutablePath+FARoundedCorner.swift in Sources */, + F37103882EA08B6F00E7F171 /* FANetworkMonitor.swift in Sources */, + F37103672E9E3ABC00E7F171 /* FASearchResultCell.swift in Sources */, + F371037B2EA0820C00E7F171 /* FASettingViewController.swift in Sources */, + F3C9AE5D2E77DED000E25109 /* FADeviceIDManager.swift in Sources */, + F3A792BA2E77E1210097E0BC /* FAAPIPath.swift in Sources */, + F37103532E9E1E9300E7F171 /* FASearchRecordView.swift in Sources */, + F37103862EA087FB00E7F171 /* FAFeedbackViewController.swift in Sources */, + F3C9AE5B2E77DDE100E25109 /* FAKeychainHelper.swift in Sources */, + F371034D2E9DF9FB00E7F171 /* FASearchViewController.swift in Sources */, + F3A792B82E77E0EF0097E0BC /* FACryptorService.swift in Sources */, + F3A7932C2E796EF80097E0BC /* FAPlayerDetailControlView.swift in Sources */, + F3A798BC2E82AB6F0097E0BC /* FAEpSelectorView.swift in Sources */, + F37103512E9E1D7800E7F171 /* FASearchHomeView.swift in Sources */, + F3A792AF2E77DFB90097E0BC /* FATokenModel.swift in Sources */, + F38C25FA2E86A217008C22C3 /* UIView+FAAdd.swift in Sources */, + F3A7931A2E7911420097E0BC /* FAPagerViewTransformer.swift in Sources */, + F3DCC0672E8AA14A00D58007 /* FAMeItemModel.swift in Sources */, + F37103312E978F8C00E7F171 /* FACollectViewController.swift in Sources */, + F301F6472E974B6300E76A90 /* FARecommendPlayerControlView.swift in Sources */, + F3A792D62E77F70C0097E0BC /* FAShortPlayModel.swift in Sources */, + F37103472E9CF9EE00E7F171 /* FAHistoryCell.swift in Sources */, + F37103762E9FA15B00E7F171 /* FAAboutCell.swift in Sources */, + F37103412E97C20C00E7F171 /* UINavigationBar+FAAdd.swift in Sources */, + F3A792D82E77F70C0097E0BC /* FAShortDetailViewModel.swift in Sources */, + F37103802EA0839500E7F171 /* FABaseWebViewController.swift in Sources */, + F3A793002E77FA0C0097E0BC /* SwiftUIExtension.swift in Sources */, + F3A792D92E77F70C0097E0BC /* FAShortDetailModel.swift in Sources */, + F3A792DA2E77F70C0097E0BC /* FAPlayerDetailCell.swift in Sources */, + F3A792DB2E77F70C0097E0BC /* FAVideoInfoModel.swift in Sources */, + F3A7930C2E7813FE0097E0BC /* FACollectionView.swift in Sources */, + F37103822EA0868100E7F171 /* FAAppWebViewController.swift in Sources */, + F37103712E9F964000E7F171 /* FALocalized.swift in Sources */, + F3A792DC2E77F70C0097E0BC /* FAPlayerDetailViewController.swift in Sources */, + F38C25FF2E86B663008C22C3 /* AppDelegate+FAConfig.swift in Sources */, + F37103592E9E281C00E7F171 /* FASearchRecommendView.swift in Sources */, + F3A792BF2E77E4B80097E0BC /* FATool.swift in Sources */, + F371033F2E97BDF800E7F171 /* FAHistoryViewController.swift in Sources */, + F3DCC08B2E8BB16F00D58007 /* FARecommendViewController.swift in Sources */, + F3DCC0492E8A65B000D58007 /* FAEpSelectorCell.swift in Sources */, + F3A793162E790CC90097E0BC /* FAHomeBannerCell.swift in Sources */, + F3DCC04B2E8A6EAE00D58007 /* FAEpMenuView.swift in Sources */, + F3DCC0632E8A9E7600D58007 /* FAMeCell.swift in Sources */, + F3A793142E78FC4C0097E0BC /* FAHomeBannerContentCell.swift in Sources */, + F39E66372E77BAD0008AAAFA /* AppDelegate.swift in Sources */, + F3DCC08E2E8BB1F100D58007 /* FARecommendViewModel.swift in Sources */, + F371036B2E9E530400E7F171 /* UIStackView+FAAdd.swift in Sources */, + F3A792C42E77E8070097E0BC /* FATabBarController.swift in Sources */, + F3A792BC2E77E2EE0097E0BC /* String+FAAdd.swift in Sources */, + F3A792AD2E77DF830097E0BC /* FAUserInfo.swift in Sources */, + F3A792DF2E77F7EA0097E0BC /* FAAPI.swift in Sources */, + F3A7932A2E795B4C0097E0BC /* FAHomeSectionTitleView.swift in Sources */, + F3DCC05C2E8A9C1B00D58007 /* FATableView.swift in Sources */, + F37103492E9DD98600E7F171 /* UIScrollView+FARefresh.swift in Sources */, + F3A7931E2E793D000097E0BC /* FAHomeNewContentCell.swift in Sources */, + F39E66382E77BAD0008AAAFA /* SceneDelegate.swift in Sources */, + F371036E2E9E6E7800E7F171 /* FAEmpty.swift in Sources */, + F37103362E97929F00E7F171 /* FACollectCell.swift in Sources */, + F3A793532E7BA54A0097E0BC /* FAPlayerEpUIButton.swift in Sources */, + F3A792EA2E77F8820097E0BC /* FAHomeViewModel.swift in Sources */, + F3A792C82E77EED10097E0BC /* Font+FAAdd.swift in Sources */, + F3A792C12E77E6BA0097E0BC /* UIScreen+FAAdd.swift in Sources */, + F3DCC0912E8BBB7600D58007 /* FARecommendPlayerCell.swift in Sources */, + F3A798B92E828F5E0097E0BC /* FAPlayerProgressView.swift in Sources */, + F371035F2E9E2E7400E7F171 /* FASearchRecommendCell.swift in Sources */, + F3A793092E7812F60097E0BC /* FAWaterfallFlowLayout.swift in Sources */, + F37103732E9F9E0D00E7F171 /* FAAboutViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + F39E66312E77BAD0008AAAFA /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + F39E66302E77BAD0008AAAFA /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + F3A798B52E828C180097E0BC /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + F3A798B62E828C180097E0BC /* en */, + ); + name = Localizable.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + F39E65F92E77B826008AAAFA /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 196896C43C7C7895E473B9EB /* Pods-Fableon.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8NNUR9HPV3; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Fableon/Source/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Fableon; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = aaaaaa.Fableon; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "Fableon/Source/Fableon-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + F39E65FA2E77B826008AAAFA /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DC14083E24B746ED3DE2FE0C /* Pods-Fableon.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 8NNUR9HPV3; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Fableon/Source/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = Fableon; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = ""; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = aaaaaa.Fableon; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "Fableon/Source/Fableon-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; + F39E65FB2E77B826008AAAFA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 8NNUR9HPV3; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + F39E65FC2E77B826008AAAFA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 8NNUR9HPV3; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F39E65E02E77B824008AAAFA /* Build configuration list for PBXProject "Fableon" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F39E65FB2E77B826008AAAFA /* Debug */, + F39E65FC2E77B826008AAAFA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F39E65F82E77B826008AAAFA /* Build configuration list for PBXNativeTarget "Fableon" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F39E65F92E77B826008AAAFA /* Debug */, + F39E65FA2E77B826008AAAFA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = F39E65DD2E77B824008AAAFA /* Project object */; +} diff --git a/Fableon/App/AppDelegate+FAConfig.swift b/Fableon/App/AppDelegate+FAConfig.swift new file mode 100644 index 0000000..8c3dcd1 --- /dev/null +++ b/Fableon/App/AppDelegate+FAConfig.swift @@ -0,0 +1,33 @@ +// +// AppDelegate+FAConfig.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/26. +// + +import UIKit +import MJRefresh +import IQKeyboardManagerSwift +import IQKeyboardToolbarManager + + +extension AppDelegate { + + func fa_config() { + UIView.fa_Awake() + + FAToast.config() + + //设置刷新控件的语言 + MJRefreshConfig.default.languageCode = "en" + + IQKeyboardManager.shared.isEnabled = true + IQKeyboardManager.shared.resignOnTouchOutside = true + IQKeyboardToolbarManager.shared.isEnabled = false + + let appearance = UINavigationBarAppearance.defaultAppearance() + UINavigationBar.appearance().scrollEdgeAppearance = appearance + UINavigationBar.appearance().standardAppearance = appearance + } + +} diff --git a/Fableon/App/AppDelegate.swift b/Fableon/App/AppDelegate.swift new file mode 100644 index 0000000..da7a670 --- /dev/null +++ b/Fableon/App/AppDelegate.swift @@ -0,0 +1,52 @@ +// +// AppDelegate.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + FANetworkMonitor.manager.startMonitoring() + self.fa_config() + + NotificationCenter.default.addObserver(self, selector: #selector(networkStatusDidChangeNotification), name: FANetworkMonitor.networkStatusDidChangeNotification, object: nil) + + DispatchQueue.main.asyncAfter(wallDeadline: .now() + 0.2) { + if FANetworkMonitor.manager.isReachable == true { + FALogin.manager.requestUserInfo(completer: nil) + } + } + + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + @objc private func networkStatusDidChangeNotification() { + + if FANetworkMonitor.manager.isReachable == true { + FALogin.manager.requestUserInfo(completer: nil) + } + + } +} + diff --git a/Fableon/App/SceneDelegate.swift b/Fableon/App/SceneDelegate.swift new file mode 100644 index 0000000..1e62cd1 --- /dev/null +++ b/Fableon/App/SceneDelegate.swift @@ -0,0 +1,54 @@ +// +// SceneDelegate.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + guard let windowScene = (scene as? UIWindowScene) else { return } + FATool.windowScene = windowScene + + window = UIWindow(windowScene: windowScene) + window?.rootViewController = FATabBarController() + window?.makeKeyAndVisible() + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/Fableon/Base/Controller/FANavigationController.swift b/Fableon/Base/Controller/FANavigationController.swift new file mode 100644 index 0000000..b3275d1 --- /dev/null +++ b/Fableon/Base/Controller/FANavigationController.swift @@ -0,0 +1,43 @@ +// +// FANavigationController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit +import FDFullscreenPopGesture + +class FANavigationController: UINavigationController { + + override func viewDidLoad() { + super.viewDidLoad() + fd_fullscreenPopGestureRecognizer.isEnabled = true + } + + override func pushViewController(_ viewController: UIViewController, animated: Bool) { + if children.count > 0 { + viewController.hidesBottomBarWhenPushed = true + } + super.pushViewController(viewController, animated: animated) + } + + override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { + for (index, value) in viewControllers.enumerated() { + if index != 0 { + value.hidesBottomBarWhenPushed = true + } + } + super.setViewControllers(viewControllers, animated: animated) + } + + + override var childForStatusBarStyle: UIViewController? { + return self.topViewController + } + + override var childForStatusBarHidden: UIViewController? { + return self.topViewController + } + +} diff --git a/Fableon/Base/Controller/FATabBarController.swift b/Fableon/Base/Controller/FATabBarController.swift new file mode 100644 index 0000000..c8ec2f8 --- /dev/null +++ b/Fableon/Base/Controller/FATabBarController.swift @@ -0,0 +1,64 @@ +// +// FATabBarController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit + +class FATabBarController: UITabBarController { + + override func viewDidLoad() { + super.viewDidLoad() + + let nav1 = getNavigation(FAHomeViewController(), "Home".localized, UIImage(named: "tabbar_home_icon"), UIImage(named: "tabbar_home_icon_selected")) + let nav2 = getNavigation(FARecommendViewController(), "Recommend".localized, UIImage(named: "tabbar_recommend_icon"), UIImage(named: "tabbar_recommend_icon_selected")) + let nav3 = getNavigation(FACollectViewController(), "Collect".localized, UIImage(named: "tabbar_collect_icon"), UIImage(named: "tabbar_collect_icon_selected")) + let nav4 = getNavigation(FAMeViewController(), "Me".localized, UIImage(named: "tabbar_me_icon"), UIImage(named: "tabbar_me_icon_selected")) + + viewControllers = [nav1, nav2, nav3, nav4] + + + let appearance = UITabBarAppearance() + appearance.backgroundColor = .init(named: .color_0D0D0D) + appearance.backgroundImage = UIImage() + appearance.shadowColor = .clear + appearance.shadowImage = UIImage() + appearance.stackedLayoutAppearance.normal.titleTextAttributes = [ + .font : UIFont.font(ofSize: 10, weight: .init(500)), + .foregroundColor : UIColor(named: .color_777777)! + ] + appearance.stackedLayoutAppearance.selected.titleTextAttributes = [ + .font : UIFont.font(ofSize: 10, weight: .init(500)), + .foregroundColor : UIColor(named: .color_3769FC)! + ] + + + self.tabBar.scrollEdgeAppearance = appearance + self.tabBar.standardAppearance = appearance + self.tabBar.isTranslucent = false + + } + + override var childForStatusBarStyle: UIViewController? { + return self.selectedViewController + } + + override var childForStatusBarHidden: UIViewController? { + return self.selectedViewController + } +} + + +extension FATabBarController { + + private func getNavigation(_ viewController: UIViewController, _ title: String, _ image: UIImage?, _ selectedImage: UIImage?) -> UINavigationController { + let nav = FANavigationController(rootViewController: viewController) + nav.tabBarItem.title = title + nav.tabBarItem.image = image + nav.tabBarItem.selectedImage = selectedImage + return nav + } + +} diff --git a/Fableon/Base/Controller/FAViewController.swift b/Fableon/Base/Controller/FAViewController.swift new file mode 100644 index 0000000..144f65f --- /dev/null +++ b/Fableon/Base/Controller/FAViewController.swift @@ -0,0 +1,93 @@ +// +// FAViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit + +class FAViewController: UIViewController { + + lazy var bgView: UIView = { + let view = UIImageView(image: UIImage(named: "背景")) + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.edgesForExtendedLayout = [.top] + + if let navi = navigationController { + if navi.visibleViewController == self { + if navi.viewControllers.count > 1 { + configNavigationBack() + } + } + } + + view.addSubview(bgView) + bgView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + + func handleHeaderRefresh(_ completer: (() -> Void)?) { + completer?() + } + + func handleFooterRefresh(_ completer: (() -> Void)?) { + completer?() + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + +} + +extension UIViewController { + func configNavigationBack(_ imageName: String = "Frame 3011") { + let image = UIImage(named: imageName) + + let leftBarButtonItem = UIBarButtonItem(image: image, style: .plain ,target: self,action: #selector(handleNavigationBack)) + navigationItem.leftBarButtonItem = leftBarButtonItem + } + + @objc func handleNavigationBack() { + self.fa_toLastViewController(animated: true) + } + + func fa_toLastViewController(animated: Bool) { + if self.navigationController != nil + { + if self.navigationController?.viewControllers.count == 1 + { + self.dismiss(animated: animated, completion: nil) + } else { + self.navigationController?.popViewController(animated: animated) + } + } + else if self.presentingViewController != nil { + self.dismiss(animated: animated, completion: nil) + } + } +} + +extension UIViewController { + + func fa_setNavigationStyle(backgroundColor: UIColor = .clear, + titleFont: UIFont = .font(ofSize: 18, weight: .bold), + titleColor: UIColor = .FFFFFF, + isTranslucent: Bool = true + ) { + self.navigationController?.navigationBar.fa_setTranslucent(isTranslucent: isTranslucent) + self.navigationController?.navigationBar.fa_setBackgroundColor(backgroundColor: backgroundColor) + self.navigationController?.navigationBar.fa_setTitleTextAttributes(titleTextAttributes: [ + NSAttributedString.Key.font : titleFont, + NSAttributedString.Key.foregroundColor : titleColor + ]) + } + +} diff --git a/Fableon/Base/Define/FADefine.swift b/Fableon/Base/Define/FADefine.swift new file mode 100644 index 0000000..7f3aa81 --- /dev/null +++ b/Fableon/Base/Define/FADefine.swift @@ -0,0 +1,39 @@ +// +// FADefine.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/26. +// + +import UIKit + + +///当前系统版本号 +let kFAOsVersion: String = UIDevice.current.systemVersion +let kBRAPPBundleIdentifier: String = (Bundle.main.infoDictionary!["CFBundleIdentifier"] as? String) ?? "0" + +///app版本号 +let kFAAPPVersion: String = (Bundle.main.infoDictionary!["CFBundleShortVersionString"] as? String) ?? "0" +let kFAAPPBundleVersion: String = (Bundle.main.infoDictionary!["CFBundleVersion"] as? String) ?? "0" + +let kFAAPPBundleName: String = (Bundle.main.infoDictionary!["CFBundleName"] as? String) ?? "" +let kFAAPPName: String = (Bundle.main.infoDictionary!["CFBundleDisplayName"] as? String) ?? "" + + + +public func fa_swizzled_instanceMethod(_ prefix: String, oldClass: Swift.AnyClass!, oldSelector: String, newClass: Swift.AnyClass) { + let newSelector = prefix + "_" + oldSelector; + let originalSelector = NSSelectorFromString(oldSelector) + let swizzledSelector = NSSelectorFromString(newSelector) + + let originalMethod = class_getInstanceMethod(oldClass, originalSelector) + let swizzledMethod = class_getInstanceMethod(newClass, swizzledSelector) + + let isAdd = class_addMethod(oldClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!)) + + if isAdd { + class_replaceMethod(newClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!)) + }else { + method_exchangeImplementations(originalMethod!, swizzledMethod!) + } +} diff --git a/Fableon/Base/Extension/CGMutablePath+FARoundedCorner.swift b/Fableon/Base/Extension/CGMutablePath+FARoundedCorner.swift new file mode 100644 index 0000000..6072ce4 --- /dev/null +++ b/Fableon/Base/Extension/CGMutablePath+FARoundedCorner.swift @@ -0,0 +1,65 @@ +// +// CGMutablePath+FARoundedCorner.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/28. +// + +import UIKit + +struct FARoundedCorner { + var topLeft:CGFloat = 0 + var topRight:CGFloat = 0 + var bottomLeft:CGFloat = 0 + var bottomRight:CGFloat = 0 + + public static let zero = FARoundedCorner(topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0) + public init(topLeft: CGFloat, topRight:CGFloat, bottomLeft:CGFloat, bottomRight:CGFloat) { + self.topLeft = topLeft + self.topRight = topRight + self.bottomLeft = bottomLeft + self.bottomRight = bottomRight + } + static func ==(v1:FARoundedCorner, v2:FARoundedCorner) -> Bool { + return v1.bottomLeft == v2.bottomLeft + && v1.bottomRight == v2.bottomRight + && v1.topLeft == v2.topLeft + && v1.topRight == v2.topRight + } + static func !=(v1:FARoundedCorner, v2:FARoundedCorner) -> Bool { + return !(v1 == v2) + } +} + +extension CGMutablePath { + func addRadiusRectangle(_ circulars: FARoundedCorner, rect: CGRect) { + let minX = rect.minX + let minY = rect.minY + let maxX = rect.maxX + let maxY = rect.maxY + + //获取四个圆心 + let topLeftCenterX = minX + circulars.topLeft + let topLeftCenterY = minY + circulars.topLeft + + let topRightCenterX = maxX - circulars.topRight + let topRightCenterY = minY + circulars.topRight + + let bottomLeftCenterX = minX + circulars.bottomLeft + let bottomLeftCenterY = maxY - circulars.bottomLeft + + let bottomRightCenterX = maxX - circulars.bottomRight + let bottomRightCenterY = maxY - circulars.bottomRight + + //顶 左 + addArc(center: CGPoint(x: topLeftCenterX, y: topLeftCenterY), radius: circulars.topLeft, startAngle: CGFloat.pi, endAngle: CGFloat.pi * 3 / 2, clockwise: false) + //顶右 + addArc(center: CGPoint(x: topRightCenterX, y: topRightCenterY), radius: circulars.topRight, startAngle: CGFloat.pi * 3 / 2, endAngle: 0, clockwise: false) + //底右 + addArc(center: CGPoint(x: bottomRightCenterX, y: bottomRightCenterY), radius: circulars.bottomRight, startAngle: 0, endAngle: CGFloat.pi / 2, clockwise: false) + //底左 + addArc(center: CGPoint(x: bottomLeftCenterX, y: bottomLeftCenterY), radius: circulars.bottomLeft, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi, clockwise: false) + closeSubpath(); + + } +} diff --git a/Fableon/Base/Extension/Dictionary+FAAdd.swift b/Fableon/Base/Extension/Dictionary+FAAdd.swift new file mode 100644 index 0000000..09c1541 --- /dev/null +++ b/Fableon/Base/Extension/Dictionary+FAAdd.swift @@ -0,0 +1,23 @@ +// +// Dictionary+FAAdd.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/16. +// + +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 + } + +} diff --git a/Fableon/Base/Extension/Font+FAAdd.swift b/Fableon/Base/Extension/Font+FAAdd.swift new file mode 100644 index 0000000..5af2085 --- /dev/null +++ b/Fableon/Base/Extension/Font+FAAdd.swift @@ -0,0 +1,24 @@ +// +// Font+FAAdd.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit +import SwiftUICore + +extension Font { + + static func font(size: CGFloat, weight: Font.Weight) -> Font { + return Font.system(size: size, weight: weight) + } +} + +extension UIFont { + + static func font(ofSize: CGFloat, weight: UIFont.Weight) -> UIFont { + return UIFont.systemFont(ofSize: ofSize, weight: weight) + } + +} diff --git a/Fableon/Base/Extension/String+FAAdd.swift b/Fableon/Base/Extension/String+FAAdd.swift new file mode 100644 index 0000000..9175be2 --- /dev/null +++ b/Fableon/Base/Extension/String+FAAdd.swift @@ -0,0 +1,41 @@ +// +// String+FAAdd.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import Foundation +import SmartCodable +import YYCategories + +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) + } + + func size(_ font: UIFont, _ size: CGSize) -> CGSize { + return (self as NSString).size(for: font, size: size, mode: .byWordWrapping) + } + +} + +extension String { + static let color_FFFFFF = "#FFFFFF" + static let color_000000 = "#000000" + static let color_3769FC = "#3769FC" + static let color_777777 = "#777777" + static let color_0D0D0D = "#0D0D0D" + static let color_81CAFF = "#81CAFF" + static let color_20A2FF = "#20A2FF" + static let color_DDEDFD = "#DDEDFD" + static let color_A8DBFF = "#A8DBFF" + static let color_BEDFFF = "#BEDFFF" + static let color_52A2F1 = "#52A2F1" + static let color_C7DEF5 = "#C7DEF5" + static let color_333333 = "#333333" + static let color_D9D9D9 = "#D9D9D9" +} diff --git a/Fableon/Base/Extension/UI/SwiftUIExtension.swift b/Fableon/Base/Extension/UI/SwiftUIExtension.swift new file mode 100644 index 0000000..46f0058 --- /dev/null +++ b/Fableon/Base/Extension/UI/SwiftUIExtension.swift @@ -0,0 +1,80 @@ +// +// SwiftUIExtension.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import SwiftUI + +struct FAOnFirstAppear: ViewModifier { + @State private var hasAppeared = false + let action: () -> Void + + func body(content: Content) -> some View { + content.onAppear { + if !hasAppeared { + hasAppeared = true + action() + } + } + } +} + +struct FAGradientBorder: ViewModifier { + var colors: [Color] = [.red, .blue] + var lineWidth: CGFloat = 4 + var cornerRadius: CGFloat = 16 + var startPoint: UnitPoint = .topLeading + var endPoint: UnitPoint = .bottomTrailing + + func body(content: Content) -> some View { + content + .overlay( + RoundedRectangle(cornerRadius: cornerRadius) + .stroke( + LinearGradient( + gradient: Gradient(colors: colors), + startPoint: startPoint, + endPoint: endPoint + ), + lineWidth: lineWidth + ) + ) + .clipShape(RoundedRectangle(cornerRadius: cornerRadius)) + } +} + + +extension View { + func onFirstAppear(perform action: @escaping () -> Void) -> some View { + self.modifier(FAOnFirstAppear(action: action)) + } + + func gradientBorder( + colors: [Color] = [.red, .blue], + lineWidth: CGFloat = 4, + cornerRadius: CGFloat = 16, + startPoint: UnitPoint = .topLeading, + endPoint: UnitPoint = .bottomTrailing + ) -> some View { + self.modifier(FAGradientBorder(colors: colors, + lineWidth: lineWidth, + cornerRadius: cornerRadius, + startPoint: startPoint, + endPoint: endPoint)) + } + + func setBackground() -> some View { + self.background( + Image("背景") + .resizable() + .scaledToFill() + .ignoresSafeArea() + .clipped() + ) + } + + + +} diff --git a/Fableon/Base/Extension/UINavigationBar+FAAdd.swift b/Fableon/Base/Extension/UINavigationBar+FAAdd.swift new file mode 100644 index 0000000..4e3de55 --- /dev/null +++ b/Fableon/Base/Extension/UINavigationBar+FAAdd.swift @@ -0,0 +1,49 @@ +// +// UINavigationBar+FAAdd.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/9. +// + +import UIKit + +extension UINavigationBarAppearance { + static func defaultAppearance() -> UINavigationBarAppearance { + let navBarAppearance = UINavigationBarAppearance() + navBarAppearance.configureWithOpaqueBackground() + navBarAppearance.backgroundColor = .clear + navBarAppearance.backgroundEffect = nil + navBarAppearance.shadowColor = UIColor.clear + navBarAppearance.titleTextAttributes = [ + NSAttributedString.Key.font : UIFont.font(ofSize: 18, weight: .bold), + NSAttributedString.Key.foregroundColor : UIColor.FFFFFF + ] + return navBarAppearance + } +} + +extension UINavigationBar { + + + func fa_setTranslucent(isTranslucent: Bool) { + self.isTranslucent = isTranslucent + } + + func fa_setBackgroundColor(backgroundColor: UIColor?) { + let appearance = self.standardAppearance + appearance.backgroundColor = backgroundColor + self.standardAppearance = appearance + self.scrollEdgeAppearance = appearance + } + + func fa_setTitleTextAttributes(titleTextAttributes: [NSAttributedString.Key : Any]?) { + let appearance = self.standardAppearance + + if let titleTextAttributes = titleTextAttributes { + appearance.titleTextAttributes = titleTextAttributes + } + self.scrollEdgeAppearance = appearance + self.standardAppearance = appearance + + } +} diff --git a/Fableon/Base/Extension/UIScreen+FAAdd.swift b/Fableon/Base/Extension/UIScreen+FAAdd.swift new file mode 100644 index 0000000..758882b --- /dev/null +++ b/Fableon/Base/Extension/UIScreen+FAAdd.swift @@ -0,0 +1,40 @@ +// +// Screen+FAAdd.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + + +import UIKit + +extension UIScreen { + + static var screen: UIScreen { + return UIScreen.main + } + + static var width: CGFloat { + return UIScreen.main.bounds.width + } + + static var height: CGFloat { + return UIScreen.main.bounds.height + } + + static var safeTop: CGFloat { + return FATool.keyWindow?.safeAreaInsets.top ?? 20 + } + + static var safeBottom: CGFloat { + return FATool.keyWindow?.safeAreaInsets.bottom ?? 0 + } + + static var navBarHeight: CGFloat { + return safeTop + 44 + } + + static var tabBarHeight: CGFloat { + return safeBottom + 49 + } +} diff --git a/Fableon/Base/Extension/UIScrollView+FARefresh.swift b/Fableon/Base/Extension/UIScrollView+FARefresh.swift new file mode 100644 index 0000000..35e94ca --- /dev/null +++ b/Fableon/Base/Extension/UIScrollView+FARefresh.swift @@ -0,0 +1,70 @@ +// +// UIScrollView+FARefresh.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import MJRefresh + +extension UIScrollView { + + func fa_addRefreshHeader(insetTop: CGFloat = 0, block: (() -> Void)?) { + + self.mj_header = MJRefreshNormalHeader(refreshingBlock: { + block?() + }) + self.mj_header?.ignoredScrollViewContentInsetTop = insetTop + } + + func fa_addRefreshFooter(insetBottom: CGFloat = 0, block: (() -> Void)?) { + let footer = MJRefreshAutoNormalFooter(refreshingBlock: { + block?() + }) + footer.ignoredScrollViewContentInsetBottom = insetBottom + + self.mj_footer = footer + } + + + func fa_addRefreshBackFooter(insetBottom: CGFloat = 0, block: (() -> Void)?) { + self.mj_footer = MJRefreshBackNormalFooter(refreshingBlock: { + block?() + }) + + self.mj_footer?.ignoredScrollViewContentInsetBottom = insetBottom + } + + func fa_endHeaderRefreshing() { + self.mj_header?.endRefreshing() + } + + func fa_endFooterRefreshing() { + if self.mj_footer?.state == .noMoreData { return } + self.mj_footer?.endRefreshing() + } + + ///重置没有更多 + func fa_resetNoMoreData() { + self.mj_footer?.resetNoMoreData() + } + + func fa_endRefreshingWithNoMoreData() { + self.mj_footer?.endRefreshingWithNoMoreData() + } + + func fa_updateNoMoreDataState(_ hasNextPage: Bool?) { + if hasNextPage == false { + self.fa_endRefreshingWithNoMoreData() + } else { + self.fa_resetNoMoreData() + } + + if self.mj_totalDataCount() == 0 { + self.mj_footer?.isHidden = true + } else { + self.mj_footer?.isHidden = false + } + + } +} diff --git a/Fableon/Base/Extension/UIStackView+FAAdd.swift b/Fableon/Base/Extension/UIStackView+FAAdd.swift new file mode 100644 index 0000000..12519a4 --- /dev/null +++ b/Fableon/Base/Extension/UIStackView+FAAdd.swift @@ -0,0 +1,21 @@ +// +// UIStackView+FAAdd.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit + + +extension UIStackView { + + func fa_removeAllArrangedSubview() { + let arrangedSubviews = self.arrangedSubviews + + arrangedSubviews.forEach { + self.removeArrangedSubview($0) + $0.removeFromSuperview() + } + } +} diff --git a/Fableon/Base/Extension/UIView+FAAdd.swift b/Fableon/Base/Extension/UIView+FAAdd.swift new file mode 100644 index 0000000..67aafe6 --- /dev/null +++ b/Fableon/Base/Extension/UIView+FAAdd.swift @@ -0,0 +1,95 @@ +// +// UIView+FAAdd.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/26. +// + +import UIKit + +extension UIView { + fileprivate struct AssociatedKeys { + static var fa_roundedCorner: Int? + static var fa_effect: Int? + } + + @objc public static func fa_Awake() { + fa_swizzled_instanceMethod("fa", oldClass: self, oldSelector: "layoutSubviews", newClass: self) + } + + @objc func fa_layoutSubviews() { + fa_layoutSubviews() + + _updateRoundedCorner() + + if let effectView = effectView, effectView.frame != self.bounds { + effectView.frame = self.bounds + } + } +} + +//MARK: -------------- 圆角 -------------- +extension UIView { + + + private var roundedCorner: FARoundedCorner? { + get { + return objc_getAssociatedObject(self, &AssociatedKeys.fa_roundedCorner) as? FARoundedCorner + } + set { + objc_setAssociatedObject(self, &AssociatedKeys.fa_roundedCorner, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + ///设置圆角 + func fa_setRoundedCorner(topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) { + //清空其它设置方法 + self.roundedCorner = FARoundedCorner(topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight) + _updateRoundedCorner() + } + + private func _updateRoundedCorner() { + guard let roundedCorner = self.roundedCorner else { return } + let rect = self.bounds + + let path = CGMutablePath() + path.addRadiusRectangle(roundedCorner, rect: rect) + + let maskLayer = CAShapeLayer() + maskLayer.frame = self.bounds + maskLayer.path = path + self.layer.mask = maskLayer + } + +} + + + +//MARK: -------------- 模糊效果 -------------- +extension UIView { + private var effectView: UIVisualEffectView? { + get { + return objc_getAssociatedObject(self, &AssociatedKeys.fa_effect) as? UIVisualEffectView + } + set { + objc_setAssociatedObject(self, &AssociatedKeys.fa_effect, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + + ///添加模糊效果 + func fa_addEffectView(style: UIBlurEffect.Style = .light) { + if self.effectView == nil { + let blur = UIBlurEffect(style: style) + let effectView = UIVisualEffectView(effect: blur) + effectView.isUserInteractionEnabled = false + self.addSubview(effectView) + self.sendSubviewToBack(effectView) + + self.effectView = effectView + } + } + ///删除模糊效果 + func fa_removeEffectView() { + self.effectView?.removeFromSuperview() + self.effectView = nil + } +} diff --git a/Fableon/Base/Extension/UserDefaults+FAAdd.swift b/Fableon/Base/Extension/UserDefaults+FAAdd.swift new file mode 100644 index 0000000..a695961 --- /dev/null +++ b/Fableon/Base/Extension/UserDefaults+FAAdd.swift @@ -0,0 +1,39 @@ +// +// UserDefaults+FAAdd.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import Foundation + +extension UserDefaults { + + static func fa_setObject(_ obj: NSSecureCoding?, forKey key: String) { + let defaults = UserDefaults.standard + guard let obj = obj else { + defaults.removeObject(forKey: key) + return + } + do { + let data = try NSKeyedArchiver.archivedData(withRootObject: obj, requiringSecureCoding: true) + defaults.set(data, forKey: key) + } catch { + print("Error archiving object: \(error)") + } + } + + static func fa_object(forKey key: String, as type: T.Type) -> T? { + let defaults = UserDefaults.standard + guard let data = defaults.data(forKey: key) else { + return nil + } + do { + let object = try NSKeyedUnarchiver.unarchivedObject(ofClass: type, from: data) + return object + } catch { + print("Error unarchiving object: \(error)") + return nil + } + } +} diff --git a/Fableon/Base/Request/FAAPI/FAAPI.swift b/Fableon/Base/Request/FAAPI/FAAPI.swift new file mode 100644 index 0000000..71fe0f2 --- /dev/null +++ b/Fableon/Base/Request/FAAPI/FAAPI.swift @@ -0,0 +1,161 @@ +// +// FAAPI.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit + +extension FAAPI { + ///更新短剧关注状态 [ "state" : isCollect, "id" : shortPlayId,] + static let updateShortCollectStateNotification = NSNotification.Name(rawValue: "FAAPI.updateShortCollectStateNotification") + +} + +struct FAAPI { + + static func requestHomeModulesData(completer: ((_ list: [FAHomeModuleItem]?) -> Void)?) { + FANetworkManager.manager.request(FABaseURL + "/home/all-modules", method: .get) { (response: FANetworkManager.Response>) in + completer?(response.data?.list) + } + } + + static func requestShortDetailData(shortPlayId: String, activityId: String? = nil, completer: ((FAShortDetailModel?, Int?, String?) -> Void)?) { + + var parameters: [String : Any] = [ + "short_play_id" : shortPlayId, + "video_id" : "0" + ] + + if let activityId = activityId { + parameters["activity_id"] = activityId + } + + FANetworkManager.manager.request(FABaseURL + "/getVideoDetails", method: .get, parameters: parameters) { (response: FANetworkManager.Response) in + if response.isSuccess { + completer?(response.data, response.code, response.msg) + } else { + completer?(nil, response.code, response.msg) + } + } + + } + + static func requestCreatePlayHistory(videoId: String?, shortPlayId: String?) { + guard let shortPlayId = shortPlayId else { return } + + let parameters = [ + "video_id" : videoId ?? "0", + "short_play_id" : shortPlayId + ] + + FANetworkManager.manager.request(FABaseURL + "/createHistory", method: .post, parameters: parameters, isToast: false) { (response: FANetworkManager.Response) in + + } + } + + ///历史记录列表 + static func requestPlayHistorys(page: Int, pageSize: Int = 20, completer: ((_ listModel: FANetworkManager.List?) -> Void)?) { + + let parameters = [ + "current_page" : page, + "page_size" : pageSize + ] + + FANetworkManager.manager.request(FABaseURL + "/myHistorys", method: .get, parameters: parameters, isToast: false) { (response: FANetworkManager.Response>) in + completer?(response.data) + } + } + + ///收藏 + static func requestShortCollect(isCollect: Bool, shortPlayId: String, videoId: String?, isLoding: Bool = true, success: (() -> Void)?, failure: (() -> Void)? = nil) { + let path: String + if isCollect { + path = "/collect" + } else { + path = "/cancelCollect" + } + + var parameters: [String : Any] = [ + "short_play_id" : shortPlayId, + ] + + if let videoId = videoId { + parameters["video_id"] = videoId + } + + FANetworkManager.manager.request(FABaseURL + path, + parameters: parameters, + isLoding: true) { (response: FANetworkManager.Response) in + if response.isSuccess { + success?() + NotificationCenter.default.post(name: FAAPI.updateShortCollectStateNotification, object: nil, userInfo: [ + "state" : isCollect, + "id" : shortPlayId, + ]) + } else { + failure?() + } + } + } + + ///推荐短剧 + static func requestRecommendVideo(page: Int, completer: ((_ listModel: FANetworkManager.List?) -> Void)?) { + + let parameters: [String : Any] = [ + "page_size" : 20, + "current_page" : page + ] + + FANetworkManager.manager.request(FABaseURL + "/getRecommands", + method: .get, + parameters: parameters, + isLoding: false) { (response: FANetworkManager.Response>) in + completer?(response.data) + } + } + + ///收藏列表 + static func requestCollectList(page: Int, completer: ((_ listModel: FANetworkManager.List?) -> Void)?) { + let parameters: [String : Any] = [ + "page_size" : 20, + "current_page" : page + ] + + FANetworkManager.manager.request(FABaseURL + "/myCollections", + method: .get, + parameters: parameters, + isLoding: false) { (response: FANetworkManager.Response>) in + completer?(response.data) + } + } + + ///热门搜索 + static func requestHotSearchData(completer: ((_ list: [FAShortPlayModel]?) -> Void)?) { + + FANetworkManager.manager.request(FABaseURL + "/search/hots", + method: .get, + parameters: nil, + isToast: false) { (response: FANetworkManager.Response>) in + completer?(response.data?.list) + } + } + + ///搜索 + static func requestSearch(text: String, completer: ((_ list: [FAShortPlayModel]?) -> Void)?) { + let parameters = [ + "search" : text + ] + + FANetworkManager.manager.request(FABaseURL + "/search", + method: .get, + parameters: parameters, + isToast: true) { (response: FANetworkManager.Response>) in + completer?(response.data?.list) + } + } +} + + + diff --git a/Fableon/Base/Request/FAAPIPath.swift b/Fableon/Base/Request/FAAPIPath.swift new file mode 100644 index 0000000..007e1e3 --- /dev/null +++ b/Fableon/Base/Request/FAAPIPath.swift @@ -0,0 +1,20 @@ +// +// FAAPIPath.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +let FABaseURL = "https://api-breeltv.breeltv.com/reel" +let FAWebBaseURL = "https://www.breeltv.com" +let FACampaignWebURL = "https://campaign.breeltv.com" + + + + +///反馈首页 +let kFAFeedBackHomeWebUrl = FACampaignWebURL + "/pages/leave/index" +///反馈列表 +let kFAFeedBackListWebUrl = FACampaignWebURL + "/pages/leave/list" +///反馈详情 +let kFAFeedBackDetailWebUrl = FACampaignWebURL + "/pages/leave/detail" diff --git a/Fableon/Base/Request/FACryptorService.swift b/Fableon/Base/Request/FACryptorService.swift new file mode 100644 index 0000000..3743b19 --- /dev/null +++ b/Fableon/Base/Request/FACryptorService.swift @@ -0,0 +1,95 @@ +// +// FACryptorService.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import Foundation + +struct FACryptorService { + + static func decrypt(data: String) -> String { + guard data.hasPrefix("$") else { + return data + } + + let decryptedData = deStrBytes(data: data) + return String(data: decryptedData, encoding: .utf8) ?? "" + } + + static func deStrBytes(data: String) -> Data { + let hexData = String(data.dropFirst()) + var bytes = Data() + + var index = hexData.startIndex + while index < hexData.endIndex { + let nextIndex = hexData.index(index, offsetBy: 2, limitedBy: hexData.endIndex) ?? hexData.endIndex + let byteString = String(hexData[index.. Data { + guard !data.isEmpty else { + return data + } + + let saltLen = Int(data[data.startIndex]) + guard data.count >= 1 + saltLen else { + return data + } + + let salt = data.subdata(in: 1..<1+saltLen) + let encryptedData = data.subdata(in: 1+saltLen.. Data { + let decryptedData = cxEd(data: data) + return removeSalt(data: decryptedData, salt: salt) + } + + // 加密/解密数据(按位取反) + static func cxEd(data: Data) -> Data { + return Data(data.map { $0 ^ 0xFF }) + } + + // 从数据中移除盐值 + static func removeSalt(data: Data, salt: Data) -> Data { + guard !salt.isEmpty else { + return data + } + + var result = Data() + let saltBytes = [UInt8](salt) + let saltCount = saltBytes.count + + for (index, byte) in data.enumerated() { + let saltByte = saltBytes[index % saltCount] + let decryptedByte = calRemoveSalt(v: byte, s: saltByte) + result.append(decryptedByte) + } + + return result + } + + // 计算移除盐值后的字节 + static func calRemoveSalt(v: UInt8, s: UInt8) -> UInt8 { + if v >= s { + return v - s + } else { + return UInt8(0xFF) - (s - v) + 1 + } + } +} diff --git a/Fableon/Base/Request/FANetworkManager.swift b/Fableon/Base/Request/FANetworkManager.swift new file mode 100644 index 0000000..10cd49d --- /dev/null +++ b/Fableon/Base/Request/FANetworkManager.swift @@ -0,0 +1,232 @@ +// +// FANetworkManager.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import Foundation +import Alamofire +import AdSupport +import SmartCodable +import YYCategories + +/// 网络请求封装 +class FANetworkManager { + + static let manager = FANetworkManager() + + private let operationQueue = OperationQueue() + private var tokenOperation: BlockOperation? + + // 通用请求方法 + func request( + _ url: String, + method: HTTPMethod = .post, + parameters: Parameters? = nil, + isLoding: Bool = false, + isToast: Bool = true, + completion: ((_ response: FANetworkManager.Response) -> Void)? + ) { + if FALogin.manager.token == nil, !isTokenUrl(url) { + self.requestUserToken(completer: nil) + } + + if let _ = self.tokenOperation, !isTokenUrl(url) { + requestAddQueue(url, method: method, parameters: parameters, isLoding: isLoding, isToast: isToast, completion: completion) + return + } + + _request(url, method: method, parameters: parameters, isLoding: isLoding, isToast: isToast, completion: completion) + } + + private func _request( + _ url: String, + method: HTTPMethod = .get, + parameters: Parameters? = nil, + isLoding: Bool, + isToast: Bool, + completion: ((_ response: FANetworkManager.Response) -> Void)? + ) { + if isLoding { + FAHUD.show() + } + AF.request( + url, + method: method, + parameters: parameters, + encoding: method == .get ? URLEncoding.default : JSONEncoding.default, + headers: self.headers + ) + .responseString(completionHandler: { response in + if isLoding { + FAHUD.dismiss() + } + let code = response.response?.statusCode + + if code == 401 || code == 402 || code == 403 { + if self.isTokenUrl(url) { + var response = FANetworkManager.Response() + response.code = -1 + completion?(response) + } else { + self.requestUserToken { + if FALogin.manager.token != nil { + FALogin.manager.requestUserInfo(completer: nil) + } + } + + if let _ = self.tokenOperation, !self.isTokenUrl(url) { + self.requestAddQueue(url, method: method, parameters: parameters, isLoding: isLoding, isToast: isToast, completion: completion) + } + } + return + } + + + switch response.result { + case .success(let data): + let decrypted = FACryptorService.decrypt(data: data) + if let parameters = parameters { + debugLog(parameters) + } + debugLog(url) + debugLog(decrypted) + if let response = FANetworkManager.Response.deserialize(from: decrypted) { + completion?(response) + } else { + if isToast { + FAToast.show(text: "Error".localized) + } + var res = FANetworkManager.Response() + res.code = -1 + res.msg = "解析错误" + completion?(res) + } + case .failure(let error): + if isToast { + FAToast.show(text: "network_error_01".localized) + } + + var res = FANetworkManager.Response() + res.code = error.responseCode + res.msg = error.localizedDescription + completion?(res) + } + }) + } + + + + +} + +extension FANetworkManager { + + private func requestUserToken(completer: (() -> Void)?) { + guard self.tokenOperation == nil else { + completer?() + return + } + self.tokenOperation = BlockOperation(block: { + let semaphore = DispatchSemaphore(value: 0) + + FALogin.manager.requestUserToken { + do { semaphore.signal() } + self.tokenOperation = nil + completer?() + } + semaphore.wait() + }) + operationQueue.addOperation(self.tokenOperation!) + } + + private func requestAddQueue(_ url: String, + method: HTTPMethod, + parameters: Parameters?, + isLoding: Bool, + isToast: Bool, + completion: ((_ response: FANetworkManager.Response) -> Void)?) { + guard let tokenOperation = self.tokenOperation else { return } + + let requestOperation = BlockOperation { + let semaphore = DispatchSemaphore(value: 0) + self._request(url, method: method, parameters: parameters, isLoding: isLoding, isToast: isToast) { response in + semaphore.signal() + completion?(response) + } + semaphore.wait() + } + ///设置依赖关系 + requestOperation.addDependency(tokenOperation) + + operationQueue.addOperation(requestOperation) + + } + +} + +extension FANetworkManager { + + private func isTokenUrl(_ url: String) -> Bool { + return url.contains("/customer/register") + } + + private var headers: HTTPHeaders { + let token = FALogin.manager.token?.token ?? "" + let dic = [ + "authorization" : token, + "system-version" : UIDevice.current.systemVersion, + "lang-key" : "en", + "time-zone" : String.timeZone(), + "app-version" : (Bundle.main.infoDictionary!["CFBundleShortVersionString"] as? String) ?? "", + "brand" : "apple", //品牌 + "app-name" : "Fableon", + "system-type" : "ios", + "model" : UIDevice.current.machineModelName ?? "", + "idfa" : ASIdentifierManager.shared().advertisingIdentifier.uuidString, + "device-id" : FADeviceIDManager.shared.id, //设备id + "device-gaid" : UIDevice.current.identifierForVendor?.uuidString ?? "" + ] + return HTTPHeaders(dic) + } +} + + +extension FANetworkManager { + + struct Response: SmartCodable { + + var code: Int? + var data: T? + var msg: String? + + var isSuccess: Bool { + return code == 200 + } + } + + struct List: SmartCodable { + var list: [T]? + var pagination: Pagination? + + var hasNextPage: Bool { + let totalPage = pagination?.page_total ?? 0 + let currentPage = pagination?.current_page ?? 0 + return totalPage > currentPage + } + + + } + + struct Pagination: SmartCodable { + var current_page: Int? + var page_size: Int? + var page_total: Int? + var total_size: Int? + } + + +} + + diff --git a/Fableon/Base/Request/FANetworkMonitor.swift b/Fableon/Base/Request/FANetworkMonitor.swift new file mode 100644 index 0000000..885e5b7 --- /dev/null +++ b/Fableon/Base/Request/FANetworkMonitor.swift @@ -0,0 +1,73 @@ +// +// FANetworkMonitor.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/16. +// + +import UIKit +import Network + +class FANetworkMonitor { + static let manager = FANetworkMonitor() + + ///是否有网 + var isReachable: Bool? + + private var connectionType: NWInterface.InterfaceType? + private var status: NWPath.Status? + + private let monitor = NWPathMonitor() + private let queue = DispatchQueue(label: "NetworkMonitorQueue") + + func startMonitoring() { + + monitor.pathUpdateHandler = { [weak self] path in + guard let self = self else { return } + self.status = path.status + + if path.usesInterfaceType(.wifi) { + self.connectionType = .wifi + } else if path.usesInterfaceType(.cellular) { + self.connectionType = .cellular + } else if path.usesInterfaceType(.wiredEthernet) { + self.connectionType = .wiredEthernet + } else { + self.connectionType = nil + } + + + if path.status == .satisfied, self.connectionType != nil { + if self.isReachable == false { + self.isReachable = true + DispatchQueue.main.async { + NotificationCenter.default.post(name: FANetworkMonitor.networkStatusDidChangeNotification, object: nil) + } + } else { + self.isReachable = true + } + } else { + if self.isReachable == true { + self.isReachable = false + DispatchQueue.main.async { + NotificationCenter.default.post(name: FANetworkMonitor.networkStatusDidChangeNotification, object: nil) + } + } else { + self.isReachable = false + } + } + } + + + monitor.start(queue: queue) + } + + func stopMonitoring() { + monitor.cancel() + } +} + +extension FANetworkMonitor { + ///网络发生变化 + @objc static let networkStatusDidChangeNotification = NSNotification.Name(rawValue: "FANetworkMonitor.networkStatusDidChangeNotification") +} diff --git a/Fableon/Base/View/FACollectionView.swift b/Fableon/Base/View/FACollectionView.swift new file mode 100644 index 0000000..d86d0fd --- /dev/null +++ b/Fableon/Base/View/FACollectionView.swift @@ -0,0 +1,22 @@ +// +// FACollectionView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit + +class FACollectionView: UICollectionView { + + override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { + super.init(frame: frame, collectionViewLayout: layout) + self.backgroundColor = .clear + self.contentInsetAdjustmentBehavior = .never + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Fableon/Base/View/FAImageView.swift b/Fableon/Base/View/FAImageView.swift new file mode 100644 index 0000000..3fe68e3 --- /dev/null +++ b/Fableon/Base/View/FAImageView.swift @@ -0,0 +1,101 @@ +// +// FAImageView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/16. +// + +import UIKit +import Kingfisher + +@IBDesignable +class FAImageView: UIImageView { + + var placeholderColor = UIColor._8_B_8_B_8_B + var placeholderImage = UIImage(named: "placeholder_image") + + private lazy var placeholderImageView: UIImageView = { + let imageView = UIImageView(image: placeholderImage) + imageView.isHidden = true + imageView.contentMode = .scaleAspectFit + return imageView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + _init() + } + + override init(image: UIImage?) { + super.init(image: image) + _init() + } + + override init(image: UIImage?, highlightedImage: UIImage?) { + super.init(image: image, highlightedImage: highlightedImage) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + + override func awakeFromNib() { + super.awakeFromNib() + _init() + } + + func _init() { + self.contentMode = .scaleAspectFill + self.layer.masksToBounds = true + if image == nil { + self.backgroundColor = self.placeholderColor + placeholderImageView.isHidden = false + } + addSubview(placeholderImageView) + } + + override var image: UIImage? { + didSet { + if self.backgroundColor == nil && image == nil { + self.backgroundColor = self.placeholderColor + } else if image != nil { + if self.backgroundColor == self.placeholderColor { + self.backgroundColor = nil + } + } + + if image == nil { + placeholderImageView.isHidden = false + } else { + placeholderImageView.isHidden = true + } + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + placeholderImageView.frame = .init(x: 0, y: 0, width: self.bounds.width * (2 / 3), height: self.bounds.height * (2 / 3)) + placeholderImageView.center = .init(x: self.bounds.width / 2, y: self.bounds.height / 2) + } + + +} + +extension UIImageView { + func fa_setImage(_ url: String?, placeholder: UIImage? = nil, completer: ((_ image: UIImage?, _ url: URL?) -> Void)? = nil) { + + self.kf.setImage(with: URL(string: url ?? ""), placeholder: placeholder, options: nil) { result in + switch result { + case .success(let value): + completer?(value.image, value.source.url) + default : + completer?(nil, nil) + break + } + } + } +} + diff --git a/Fableon/Base/View/FAPanModalContentView.swift b/Fableon/Base/View/FAPanModalContentView.swift new file mode 100644 index 0000000..0b679d3 --- /dev/null +++ b/Fableon/Base/View/FAPanModalContentView.swift @@ -0,0 +1,78 @@ +// +// FAPanModalContentView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/23. +// + +import UIKit +import HWPanModal + +class FAPanModalContentView: HWPanModalContentView { + + + var contentHeight = UIScreen.height * (2 / 3) + + var mainScrollView: UIScrollView? + + ///更新UI contentSize发生变化时调用 + func setNeedsLayoutUpdate() { + self.panModalSetNeedsLayoutUpdate() + } + + + override init(frame: CGRect) { + super.init(frame: frame) + + self.backgroundColor = ._000000.withAlphaComponent(0.5) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //MARK: HWPanModalPresentable + override func panScrollable() -> UIScrollView? { + return mainScrollView + } + + override func longFormHeight() -> PanModalHeight { + return PanModalHeightMake(.content, contentHeight) + } + + override func showDragIndicator() -> Bool { + return false + } + + override func backgroundConfig() -> HWBackgroundConfig { + let config = HWBackgroundConfig() + config.backgroundAlpha = 0.6 + return config + } + + override func allowsTapBackgroundToDismiss() -> Bool { + return true + } + + override func allowsDragToDismiss() -> Bool { + return false + } + + override func allowsPullDownWhenShortState() -> Bool { + return false + } + + override func showsScrollableVerticalScrollIndicator() -> Bool { + return false + } + + override func springDamping() -> CGFloat { + return 1 + } + + override func cornerRadius() -> CGFloat { + return 24 + } + +} diff --git a/Fableon/Base/View/FAScrollView.swift b/Fableon/Base/View/FAScrollView.swift new file mode 100644 index 0000000..dc41a6e --- /dev/null +++ b/Fableon/Base/View/FAScrollView.swift @@ -0,0 +1,21 @@ +// +// FAScrollView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/29. +// + +import UIKit + +class FAScrollView: UIScrollView { + + override init(frame: CGRect) { + super.init(frame: frame) + self.contentInsetAdjustmentBehavior = .never + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Fableon/Base/View/FATableView.swift b/Fableon/Base/View/FATableView.swift new file mode 100644 index 0000000..09e3d45 --- /dev/null +++ b/Fableon/Base/View/FATableView.swift @@ -0,0 +1,49 @@ +// +// FATableView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/29. +// + +import UIKit + +class FATableView: UITableView { + + var insetGroupedMargins: CGFloat = 15 + + override init(frame: CGRect, style: UITableView.Style) { + super.init(frame: frame, style: style) + separatorColor = .FFFFFF.withAlphaComponent(0.4) + separatorInset = .init(top: 0, left: 16, bottom: 0, right: 16) + self.backgroundColor = .clear + self.contentInsetAdjustmentBehavior = .never + + if style == .insetGrouped { + sectionFooterHeight = 14 + sectionHeaderHeight = 0.1 + } else if style == .plain { + if #available(iOS 15.0, *) { + sectionHeaderTopPadding = 0 + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var layoutMargins: UIEdgeInsets { + set { + super.layoutMargins = newValue + } + get { + var margins = super.layoutMargins + if self.style == .insetGrouped { + margins.left = self.safeAreaInsets.left + insetGroupedMargins + margins.right = self.safeAreaInsets.right + insetGroupedMargins + } + return margins + } + } + +} diff --git a/Fableon/Base/View/FATableViewCell.swift b/Fableon/Base/View/FATableViewCell.swift new file mode 100644 index 0000000..b0afce3 --- /dev/null +++ b/Fableon/Base/View/FATableViewCell.swift @@ -0,0 +1,47 @@ +// +// FATableViewCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/29. +// + +import UIKit + +class FATableViewCell: UITableViewCell { + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + _init() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + _init() + } + + override func awakeFromNib() { + super.awakeFromNib() + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + + + private func _init() { + self.layer.rasterizationScale = UIScreen.main.scale + self.layer.shouldRasterize = true + self.selectionStyle = .none + self.backgroundColor = .clear + } +} + + +extension UITableViewCell { + + var fa_tableView: UITableView? { + return self.value(forKey: "_tableView") as? UITableView + } +} diff --git a/Fableon/Base/WebView/FAAppWebViewController.swift b/Fableon/Base/WebView/FAAppWebViewController.swift new file mode 100644 index 0000000..53698bb --- /dev/null +++ b/Fableon/Base/WebView/FAAppWebViewController.swift @@ -0,0 +1,68 @@ +// +// FAAppWebViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/16. +// + +import UIKit + +class FAAppWebViewController: FABaseWebViewController { + + + var id: String? + + private var receiveDataCount = 0 + + var theme: String? = "theme_2" + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + override func fa_webViewDidFinishLoad(_ webView: FAWebView) { + super.fa_webViewDidFinishLoad(webView) + receiveDataCount = 0 + receiveDataFromNative() + } + +} + +extension FAAppWebViewController { + + 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" : FALogin.manager.token?.token ?? "", + "time_zone" : String.timeZone(), + "lang" : FALocalized.manager.currentLocalizedKey, + "type" : "ios", + ] + + 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() + } + } + } + } + + } +} diff --git a/Fableon/Base/WebView/FABaseWebViewController.swift b/Fableon/Base/WebView/FABaseWebViewController.swift new file mode 100644 index 0000000..ec5a3b7 --- /dev/null +++ b/Fableon/Base/WebView/FABaseWebViewController.swift @@ -0,0 +1,112 @@ +// +// FABaseWebViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/16. +// + +import UIKit +import WebKit + +class FABaseWebViewController: FAViewController { + + var webUrl: String? + + ///自动设置标题 + var autoTitle = true + + var needAutoRefresh = true + + private(set) lazy var webView: FAWebView = { + let controller = WKUserContentController() + + let config = WKWebViewConfiguration() + config.userContentController = controller + config.preferences.javaScriptEnabled = true + /** 默认是不能通过JS自动打开窗口的,必须通过用户交互才能打开 */ + config.preferences.javaScriptCanOpenWindowsAutomatically = true + let webView = FAWebView(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 FABaseWebViewController { + + 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 FABaseWebViewController: FAWebViewDelegate { + + func fa_webView(_ webView: FAWebView, shouldStartLoadWith navigationAction: WKNavigationAction) -> Bool { + self.webView.isHidden = false + return true + } + + func fa_webViewDidStartLoad(_ webView: FAWebView) { + FAHUD.show(containerView: self.view) + } + + func fa_webView(webView: FAWebView, didChangeTitle title: String) { + if autoTitle { + self.title = title + } + } + + func fa_webViewDidFinishLoad(_ webView: FAWebView) { + self.webView.isHidden = false + FAHUD.dismiss() + } + + func fa_webView(_ webView: FAWebView, didFailLoadWithError error: any Error) { + FAHUD.dismiss() + } + + func fa_userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + + } +} diff --git a/Fableon/Base/WebView/FAWebView.swift b/Fableon/Base/WebView/FAWebView.swift new file mode 100644 index 0000000..b60acde --- /dev/null +++ b/Fableon/Base/WebView/FAWebView.swift @@ -0,0 +1,147 @@ +// +// FAWebView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/16. +// + +import UIKit +@preconcurrency import WebKit +import YYText + +@objc protocol FAWebViewDelegate: NSObjectProtocol { + + @objc optional func fa_webView(_ webView: FAWebView, shouldStartLoadWith navigationAction: WKNavigationAction) -> Bool + + @objc optional func fa_webViewDidStartLoad(_ webView: FAWebView) + + @objc optional func fa_webViewDidFinishLoad(_ webView: FAWebView) + + @objc optional func fa_webView(_ webView: FAWebView, didFailLoadWithError error: Error) + + ///进度 + @objc optional func fa_webView(webView: FAWebView, didChangeProgress progress: CGFloat) + ///标题 + @objc optional func fa_webView(webView: FAWebView, didChangeTitle title: String) + + ///web交互用 + @objc optional func fa_userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) + +} + +class FAWebView: WKWebView { + + weak var delegate: FAWebViewDelegate? + + private(set) var scriptMessageHandlerArray: [String] = [ + ] + + + 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? FAWebView == 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 FAWebView: 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 FAWebView: WKScriptMessageHandler { + + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + self.delegate?.fa_userContentController?(userContentController, didReceive: message) + } + +} diff --git a/Fableon/Class/Home/C/FAHomeViewController.swift b/Fableon/Class/Home/C/FAHomeViewController.swift new file mode 100644 index 0000000..b93db5c --- /dev/null +++ b/Fableon/Class/Home/C/FAHomeViewController.swift @@ -0,0 +1,247 @@ +// +// FAHomeViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit +import SwiftUI +import SnapKit + +class FAHomeViewController: FAViewController { + + private var viewModel = FAHomeViewModel() + + + + + private lazy var cvLayout: FAWaterfallFlowLayout = { + let layout = FAWaterfallFlowLayout() + layout.delegate = self + return layout + }() + + private lazy var collectionView: FACollectionView = { + let view = FACollectionView(frame: .zero, collectionViewLayout: cvLayout) + view.delegate = self + view.dataSource = self + view.contentInset = .init(top: 20, left: 0, bottom: 10, right: 0) + view.register(FAHomeBannerContentCell.self, forCellWithReuseIdentifier: "FAHomeBannerCell") + view.register(FAHomeMustSeeContentCell.self, forCellWithReuseIdentifier: "FAHomeMustSeeContentCell") + view.register(FAHomeNewContentCell.self, forCellWithReuseIdentifier: "FAHomeNewContentCell") + view.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell") + + view.register(UINib(nibName: "FAHomeRecommendedCell", bundle: nil), forCellWithReuseIdentifier: "FAHomeRecommendedCell") + view.register(UINib(nibName: "FAHomeSectionTitleView", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "FAHomeSectionTitleView") + view.register(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "footer") + view.fa_addRefreshHeader(insetTop: view.contentInset.top) { [weak self] in + self?.handleHeaderRefresh(nil) + } + return view + }() + + private lazy var titleView: UIView = { + let view = UIImageView(image: UIImage(named: "Thalire")) + return view + }() + + private lazy var searchButton: UIButton = { + let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in + guard let self = self else { return } + let vc = FASearchViewController() + self.navigationController?.pushViewController(vc, animated: true) + })) + button.setImage(UIImage(named: "首页搜索i_ic"), for: .normal) + return button + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidLoad() { + super.viewDidLoad() + NotificationCenter.default.addObserver(self, selector: #selector(networkStatusDidChangeNotification), name: FANetworkMonitor.networkStatusDidChangeNotification, object: nil) + fa_setupLayout() + + self.viewModel.requestHomeData { [weak self] in + self?.collectionView.reloadData() + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + override func handleHeaderRefresh(_ completer: (() -> Void)?) { + self.viewModel.requestHomeData { [weak self] in + self?.collectionView.reloadData() + self?.collectionView.fa_endHeaderRefreshing() + } + } + + @objc private func networkStatusDidChangeNotification() { + if self.viewModel.dataArr.isEmpty, FANetworkMonitor.manager.isReachable == true { + self.viewModel.requestHomeData { [weak self] in + self?.collectionView.reloadData() + } + } + } +} + +extension FAHomeViewController { + + private func fa_setupLayout() { + view.addSubview(titleView) + view.addSubview(searchButton) + view.addSubview(collectionView) + + titleView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerY.equalTo(self.view.snp.top).offset(UIScreen.safeTop + (UIScreen.navBarHeight - UIScreen.safeTop) / 2) + } + + searchButton.snp.makeConstraints { make in + make.centerY.equalTo(titleView) + make.right.equalToSuperview().offset(-16) + } + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + } + +} + + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension FAHomeViewController: UICollectionViewDelegate, UICollectionViewDataSource { + + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let item = self.viewModel.dataArr[indexPath.section] + + if item.type == .banner { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FAHomeBannerCell", for: indexPath) as! FAHomeBannerContentCell + cell.moduleItem = item.data as? FAHomeModuleItem + cell.viewModel = self.viewModel + return cell + + } else if item.type == .mustSee { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FAHomeMustSeeContentCell", for: indexPath) as! FAHomeMustSeeContentCell + cell.configure(self.viewModel) + return cell + + } else if item.type == .new { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FAHomeNewContentCell", for: indexPath) as! FAHomeNewContentCell + cell.configure(viewModel) + return cell + } + + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FAHomeRecommendedCell", for: indexPath) as! FAHomeRecommendedCell + + cell.model = (item.data as? FAHomeModuleItem)?.list[indexPath.row] + + return cell + } + + func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + if kind == UICollectionView.elementKindSectionHeader { + let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "FAHomeSectionTitleView", for: indexPath) + return view + } + return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "footer", for: indexPath) + } + + func numberOfSections(in collectionView: UICollectionView) -> Int { + return self.viewModel.dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + let item = self.viewModel.dataArr[section] + if item.type == .recommended { + return (item.data as? FAHomeModuleItem)?.list.count ?? 0 + } + return 1 + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let item = self.viewModel.dataArr[indexPath.section] + guard item.type == .recommended else { return } + let model = (item.data as? FAHomeModuleItem)?.list[indexPath.row] + self.viewModel.pushPlayerDetail(model) + } +} + +//MARK: FAWaterfallMutiSectionDelegate +extension FAHomeViewController: FAWaterfallMutiSectionDelegate { + + func heightForRowAtIndexPath(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat { + let homeItem = self.viewModel.dataArr[indexPath.section] + if homeItem.type == .recommended, let model = (homeItem.data as? FAHomeModuleItem)?.list[indexPath.row] { + if model.cellHeight > 0 { + return model.cellHeight + } else { + let size = CGSize.init(width: floor((UIScreen.width - 32 - 13) / 2) - 24, height: 1000) + + let height = 219 + 6 + 12 + (model.name?.size(.font(ofSize: 14, weight: .medium), size).height ?? 0) + + model.cellHeight = height + return height + } + } + return homeItem.cellHeight + } + + func columnNumber(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> Int { + let homeItem = self.viewModel.dataArr[section] + if homeItem.type == .recommended { + return 2 + } + return 1 + } + + func referenceSizeForHeader(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGSize { + let item = self.viewModel.dataArr[section] + guard item.type == .recommended else { return .zero } + return .init(width: UIScreen.width, height: 40) + } + + func spacingWithLastSection(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGFloat { + if section == 0 { + return 0 + } + return 25 + } + + func insetForSection(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> UIEdgeInsets { + let homeItem = self.viewModel.dataArr[section] + if homeItem.type == .recommended { + return .init(top: 0, left: 16, bottom: 0, right: 16) + } else { + return .zero + } + } + + func lineSpacing(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGFloat { + let homeItem = self.viewModel.dataArr[section] + if homeItem.type == .recommended { + return 12 + } else { + return 0 + } + } + + func interitemSpacing(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGFloat { + let homeItem = self.viewModel.dataArr[section] + if homeItem.type == .recommended { + return 13 + } else { + return 0 + } + } + +} diff --git a/Fableon/Class/Home/C/FASearchViewController.swift b/Fableon/Class/Home/C/FASearchViewController.swift new file mode 100644 index 0000000..60dc983 --- /dev/null +++ b/Fableon/Class/Home/C/FASearchViewController.swift @@ -0,0 +1,113 @@ +// +// FASearchViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit + +class FASearchViewController: FAViewController { + + + private lazy var viewModel: FASearchViewModel = FASearchViewModel() + + private lazy var returnButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "Frame 3011"), for: .normal) + button.addAction(UIAction(handler: { [weak self] _ in + self?.handleNavigationBack() + }), for: .touchUpInside) + return button + }() + + private lazy var textView: FASearchInputView = { + let view = FASearchInputView() + view.didSearch = { [weak self] text in + guard let self = self else { return } + self.search(text) + } + return view + }() + + private lazy var homeView: FASearchHomeView = { + let view = FASearchHomeView() + view.viewModel = viewModel + view.didSearch = { [weak self] text in + self?.textView.text = text + self?.search(text) + } + return view + }() + + private lazy var resultView: FASearchResultView = { + let view = FASearchResultView() + view.viewModel = viewModel + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + textView.becomeFirstResponder() + +// homeView.isHidden = true + resultView.isHidden = true + + fa_setupLayout() + + self.viewModel.requestSearchRecommendData(completer: nil) + } + + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + private func search(_ text: String) { + if text.isEmpty { + homeView.isHidden = false + resultView.isHidden = true + } else { + homeView.isHidden = true + resultView.isHidden = false + } + + resultView.search(text) + self.viewModel.addSearchRecord(text: text) + } + +} + +extension FASearchViewController { + + private func fa_setupLayout() { + view.addSubview(returnButton) + view.addSubview(textView) + view.addSubview(homeView) + view.addSubview(resultView) + + returnButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview().offset(UIScreen.safeTop) + make.height.equalTo(44) + } + + textView.snp.makeConstraints { make in + make.left.equalTo(returnButton.snp.right).offset(10) + make.centerY.equalTo(returnButton) + make.right.equalToSuperview().offset(-16) + } + + homeView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(returnButton.snp.bottom) + } + + resultView.snp.makeConstraints { make in + make.edges.equalTo(homeView) + } + + } + +} diff --git a/Fableon/Class/Home/M/FAHomeItem.swift b/Fableon/Class/Home/M/FAHomeItem.swift new file mode 100644 index 0000000..88c4100 --- /dev/null +++ b/Fableon/Class/Home/M/FAHomeItem.swift @@ -0,0 +1,23 @@ +// +// FAHomeItem.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/16. +// + +import UIKit + +class FAHomeItem: NSObject, Identifiable { + enum ItemType { + case banner + case mustSee + case new + case recommended + } + + var type: ItemType? + //FAHomeModuleItem [FAHomeModuleItem] + var data: Any? + + var cellHeight: CGFloat = 0 +} diff --git a/Fableon/Class/Home/M/FAHomeModuleItem.swift b/Fableon/Class/Home/M/FAHomeModuleItem.swift new file mode 100644 index 0000000..af01638 --- /dev/null +++ b/Fableon/Class/Home/M/FAHomeModuleItem.swift @@ -0,0 +1,53 @@ +// +// FAHomeModuleItem.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit +import SmartCodable + +class FAHomeModuleItem: NSObject, Identifiable, SmartCodable { + required override init() { } + + enum ModuleKey: String, SmartCaseDefaultable { + case banner = "home_banner" + case v3_recommand = "home_v3_recommand" + case new_recommand = "new_recommand" + ///分类推荐 + case cagetory_recommand = "home_cagetory_recommand" + case week_ranking = "week_ranking" + case week_recommend = "week_highest_recommend" + } + + + var module_key: ModuleKey? + var title: String? + var list: [FAShortPlayModel] = [] + + @SmartAny + var data: Any? + + + func didFinishMapping() { + if let data = data as? [[String : Any]] { + self.list = [FAShortPlayModel].deserialize(from: data) ?? [] + } else if let data = data as? [String : Any] { + var dataList: [[String : Any]]? + if let list = data["list"] as? [[String : Any]] { + self.title = data["title"] as? String + dataList = list + + } else if let list = data["shortPlayList"] as? [[String : Any]] { + self.title = data["category_name"] as? String + dataList = list + } + + if let dataList = dataList { + self.list = [FAShortPlayModel].deserialize(from: dataList) ?? [] + } + } + } + +} diff --git a/Fableon/Class/Home/UI/FAHomeMustSeeContentView.swift b/Fableon/Class/Home/UI/FAHomeMustSeeContentView.swift new file mode 100644 index 0000000..702254d --- /dev/null +++ b/Fableon/Class/Home/UI/FAHomeMustSeeContentView.swift @@ -0,0 +1,43 @@ +// +// FAHomeMustSeeContentView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/8/27. +// + +import SwiftUI + +struct FAHomeMustSeeContentView: View { + + @Binding var list: [FAShortPlayModel] + + @ObservedObject var viewModel: FAHomeViewModel + + + var body: some View { + VStack(spacing: 15) { + HStack { + Text("Editor's Picks".localized) + .font(Font.font(size: 16, weight: .medium)) + .foregroundStyle(Color(String.color_FFFFFF)) + .padding(.leading, 10) + .padding(.top, 12) + Spacer() + } + + VStack(spacing: 11) { + ForEach(list.indices, id: \.self) { index in + if index < 2 { + FAHomeMustSeeShortView(model: $list[index], viewModel: viewModel) + } + } + } + .padding(.horizontal, 10) + Spacer(minLength: 0) + } + .frame(height: 310) + .background(Color(String.color_FFFFFF).opacity(0.2)) + .cornerRadius(11) + + } +} diff --git a/Fableon/Class/Home/UI/FAHomeMustSeeShortView.swift b/Fableon/Class/Home/UI/FAHomeMustSeeShortView.swift new file mode 100644 index 0000000..d9fd114 --- /dev/null +++ b/Fableon/Class/Home/UI/FAHomeMustSeeShortView.swift @@ -0,0 +1,80 @@ +// +// FAHomeMustSeeShortView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/8/27. +// + +import SwiftUI +import Kingfisher + + +struct FAHomeMustSeeShortView: View { + + @Binding var model: FAShortPlayModel + @ObservedObject var viewModel: FAHomeViewModel + + var body: some View { + + ZStack() { + setupUI() + .onTapGesture { + self.viewModel.pushPlayerDetail(model) + } + } + } + + + + private func setupUI() -> some View { + HStack() { + KFImage(URL(string: model.image_url ?? "")) + .resizable() + .scaledToFill() + .cornerRadius(5) + .clipped() + .frame(width: 76, height: 102) + .padding(.leading, 9) + + VStack(alignment: .leading) { + Text(model.name ?? "") + .font(Font.font(size: 14, weight: .medium)) + .foregroundStyle(Color(String.color_000000)) + .lineLimit(2) + + Spacer() + + HStack(spacing: 4) { + Image("Frame 2920") + + Text("Watch".localized) + .font(Font.font(size: 12, weight: .medium)) + .foregroundStyle(Color(String.color_000000)) + + } + .padding(.trailing, 3) + .frame(maxWidth: .infinity) + .frame(height: 18) + .background(content: { + LinearGradient(colors: [Color(String.color_BEDFFF), Color(String.color_52A2F1)], startPoint: .leading, endPoint: .trailing) + }) + .cornerRadius(9) + + } + .frame(maxWidth: .infinity) + .padding(.trailing, 9) + .padding(.top, 9) + .padding(.bottom, 9) + + } + .frame(height: 120) + .frame(maxWidth: .infinity) + .background(Color(String.color_DDEDFD).opacity(0.4)) + .cornerRadius(7) + .overlay { + RoundedRectangle(cornerRadius: 7) + .stroke(Color(String.color_A8DBFF), lineWidth: 1) + } + } +} + diff --git a/Fableon/Class/Home/UI/FAHomeMustSeeView.swift b/Fableon/Class/Home/UI/FAHomeMustSeeView.swift new file mode 100644 index 0000000..80d6a34 --- /dev/null +++ b/Fableon/Class/Home/UI/FAHomeMustSeeView.swift @@ -0,0 +1,96 @@ +// +// FAHomeMustSeeView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/8/27. +// + +import SwiftUI + +struct FAHomeMustSeeView: View { + + + @State var selected = 0 +// @Binding var selected: Binding = projectedValue + + @ObservedObject var viewModel: FAHomeViewModel + + var body: some View { + VStack { + HStack { + Text("Must-see TV series".localized) + .font(Font.font(size: 18, weight: .medium)) + .foregroundStyle(Color(String.color_FFFFFF)) + .padding(.leading, 16) + Spacer() + } + Spacer(minLength: 12) + + HStack { + menuView() + + Spacer(minLength: 11) + + contentView(index: selected) + } + } + } + + private func menuView() -> some View { + let list = viewModel.mustSeeArr + + return VStack(spacing: 1) { + ForEach(list.indices, id: \.self) { index in + menuItemView(item: list[index], index: index) + } + } + .padding(.leading, 13) + } + + private func menuItemView(item: FAHomeModuleItem, index: Int) -> some View { + let isSelected = index == selected + let textColor = isSelected ? Color(String.color_FFFFFF) : Color(String.color_FFFFFF).opacity(0.8) + + return VStack { + HStack { + Text(item.title ?? "") + .font(Font.font(size: 16, weight: .black)) + .foregroundStyle(textColor) + .padding(.leading, 8) + Spacer() + } + + HStack { + Text("selection".localized) + .font(Font.font(size: 12, weight: .medium)) + .foregroundStyle(textColor) + .padding(.leading, 8) + Spacer() + } + + HStack { + Image(isSelected ? "Frame 2914" : "Frame 2916") + .padding(.leading, 8) + Spacer() + } + } + .frame(width: 105, height: 72) + .background(Color(String.color_FFFFFF).opacity(0.2)) + .cornerRadius(8) + .frame(width: 111, height: 78) + .gradientBorder(colors: isSelected ? [Color(String.color_81CAFF), Color(String.color_20A2FF)] : [Color.clear, Color.clear], lineWidth: 6, cornerRadius: 11, startPoint: .topTrailing, endPoint: .bottomLeading) + + .onTapGesture { + self.selected = index + } + } + + private func contentView(index: Int) -> some View { + return FAHomeMustSeeContentView(list: $viewModel.mustSeeArr[index].list, viewModel: self.viewModel) + .padding(.trailing, 16) + } +} + +//#Preview { +// FAHomeMustSeeView() +//} diff --git a/Fableon/Class/Home/UI/FAHomeNewView.swift b/Fableon/Class/Home/UI/FAHomeNewView.swift new file mode 100644 index 0000000..155feb5 --- /dev/null +++ b/Fableon/Class/Home/UI/FAHomeNewView.swift @@ -0,0 +1,87 @@ +// +// FAHomeNewView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/8/27. +// + +import SwiftUI +import Kingfisher +import FSPagerView + +struct FAHomeNewView: View { + + @ObservedObject var viewModel: FAHomeViewModel + + @State private var transformer: FSPagerViewTransformer = { + let transformer = FSPagerViewTransformer(type: .overlap) + transformer.minimumScale = 0.9 + transformer.minimumAlpha = 1 + return transformer + }() + + var body: some View { + let list = viewModel.homeNewItem?.list ?? [] + + VStack(spacing: 15) { + Text("New Releases".localized) + .font(.font(size: 18, weight: .medium)) + .foregroundStyle(Color(String.color_FFFFFF)) + .padding(.leading, 16) + .frame(maxWidth: .infinity, alignment: .leading) + + FSPagerSwiftUIView(list) { model in + pageContentView(model) + } + .transformer( + transformer + ) + .isLoop(true) + .itemSize(.init(width: 235, height: 235)) + .frame(height: 235) + + } + + } + + + func pageContentView(_ model: FAShortPlayModel) -> some View { + + ZStack { + GeometryReader { proxy in + KFImage(URL(string: model.image_url ?? "")) + .resizable() + .scaledToFill() + .frame(width: proxy.size.width, height: proxy.size.height) + } + VStack { + Spacer() + + HStack(spacing: 4) { + Image("Frame 2921") + + Text("Watch".localized) + .font(.font(size: 14, weight: .medium)) + .foregroundStyle(Color(String.color_000000)) + } + .frame(height: 30) + .frame(maxWidth: .infinity) + .background { + LinearGradient(colors: [Color(String.color_C7DEF5), Color(String.color_52A2F1)], startPoint: .leading, endPoint: .trailing) + } + .clipShape(RoundedRectangle(cornerRadius: 15)) + } + .padding(.trailing, 17) + .padding(.leading, 17) + .padding(.bottom, 15) + .frame(maxWidth: .infinity) + + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .onTapGesture { + self.viewModel.pushPlayerDetail(model) + } + } + +} diff --git a/Fableon/Class/Home/UI/FAHomeRecommendedItemView.swift b/Fableon/Class/Home/UI/FAHomeRecommendedItemView.swift new file mode 100644 index 0000000..e3bee21 --- /dev/null +++ b/Fableon/Class/Home/UI/FAHomeRecommendedItemView.swift @@ -0,0 +1,54 @@ +// +// FAHomeRecommendedItemView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/8/28. +// + +import SwiftUI +import Kingfisher + +struct FAHomeRecommendedItemView: View { + + @Binding var model: FAShortPlayModel + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + ZStack(alignment: .topTrailing) { + GeometryReader { proxy in + KFImage(URL(string: model.image_url ?? "")) + .resizable() + .scaledToFill() + .frame(width: proxy.size.width, height: proxy.size.height) + .clipped() + } + + if let str = model.category?.first { + ZStack { + Text(str) + .font(.font(size: 12, weight: .medium)) + .foregroundStyle(Color(String.color_FFFFFF)) + .frame(height: 24) + .padding(.horizontal, 12) + .background(Color(String.color_000000).opacity(0.75)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + } + .padding(.trailing, 6) + .padding(.top, 6) + } + + } + .frame(maxWidth: .infinity) + .frame(height: 219) + + Text(model.name ?? "") + .font(.font(size: 14, weight: .medium)) + .foregroundStyle(Color(String.color_FFFFFF)) + .padding(.init(top: 6, leading: 12, bottom: 12, trailing: 12)) + + } + .background(Color(String.color_333333)) + .clipShape(RoundedRectangle(cornerRadius: 8)) + + } +} diff --git a/Fableon/Class/Home/V/FAHomeBannerCell.swift b/Fableon/Class/Home/V/FAHomeBannerCell.swift new file mode 100644 index 0000000..2c539ff --- /dev/null +++ b/Fableon/Class/Home/V/FAHomeBannerCell.swift @@ -0,0 +1,54 @@ +// +// FAHomeBannerCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/16. +// + +import UIKit +import FSPagerView + +class FAHomeBannerCell: FSPagerViewCell { + + var model: FAShortPlayModel? { + didSet { + coverImageView.fa_setImage(model?.horizontally_img) + } + } + + + + private lazy var coverImageView: FAImageView = { + let imageView = FAImageView() + imageView.layer.cornerRadius = 12 + return imageView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + self.contentView.layer.shadowColor = UIColor.white.cgColor + self.contentView.layer.shadowRadius = 5 + self.contentView.layer.shadowOpacity = 0.75 + self.contentView.layer.shadowOffset = .init(width: 0, height: 1) + + fa_setupLayout() + } + + @MainActor required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension FAHomeBannerCell { + + private func fa_setupLayout() { + contentView.addSubview(coverImageView) + + coverImageView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + +} diff --git a/Fableon/Class/Home/V/FAHomeBannerContentCell.swift b/Fableon/Class/Home/V/FAHomeBannerContentCell.swift new file mode 100644 index 0000000..bf51c52 --- /dev/null +++ b/Fableon/Class/Home/V/FAHomeBannerContentCell.swift @@ -0,0 +1,89 @@ +// +// FAHomeBannerCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/16. +// + +import UIKit +import SwiftUI +import FSPagerView + +class FAHomeBannerContentCell: UICollectionViewCell { + + var moduleItem: FAHomeModuleItem? { + didSet { + pagerView.reloadData() + } + } + + weak var viewModel: FAHomeViewModel? + + private lazy var pagerView: FSPagerView = { + let transformer = FAPagerViewTransformer(type: .linear) + transformer.minimumAlpha = 1 + transformer.minimumScale = 0.9 + + + let view = FSPagerView() + view.itemSize = .init(width: 282, height: 146) + view.transformer = transformer + view.delegate = self + view.dataSource = self + view.isInfinite = true + view.interitemSpacing = 1 + view.register(FAHomeBannerCell.self, forCellWithReuseIdentifier: "cell") + return view + }() + + + override init(frame: CGRect) { + super.init(frame: frame) + + let subviews = pagerView.subviews.first?.subviews + subviews?.forEach { + if let view = $0 as? UICollectionView{ + view.layer.masksToBounds = false + } + } + + fa_setupLayout() + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension FAHomeBannerContentCell { + private func fa_setupLayout() { + contentView.addSubview(pagerView) + + pagerView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + +} + +//MARK: FSPagerViewDelegate FSPagerViewDataSource +extension FAHomeBannerContentCell: FSPagerViewDelegate, FSPagerViewDataSource { + func pagerView(_ pagerView: FSPagerView, cellForItemAt index: Int) -> FSPagerViewCell { + let cell = pagerView.dequeueReusableCell(withReuseIdentifier: "cell", at: index) as! FAHomeBannerCell + cell.model = moduleItem?.list[index] + return cell + } + + func numberOfItems(in pagerView: FSPagerView) -> Int { + return moduleItem?.list.count ?? 0 + } + + func pagerView(_ pagerView: FSPagerView, didSelectItemAt index: Int) { + let model = moduleItem?.list[index] + self.viewModel?.pushPlayerDetail(model) + } + + +} diff --git a/Fableon/Class/Home/V/FAHomeMustSeeContentCell.swift b/Fableon/Class/Home/V/FAHomeMustSeeContentCell.swift new file mode 100644 index 0000000..fe12231 --- /dev/null +++ b/Fableon/Class/Home/V/FAHomeMustSeeContentCell.swift @@ -0,0 +1,48 @@ +// +// FAHomeMustSeeContentCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/16. +// + +import UIKit +import SwiftUI + +class FAHomeMustSeeContentCell: UICollectionViewCell { + + + private var hostingVC: UIHostingController? + + override init(frame: CGRect) { + super.init(frame: frame) + + + } + + + func configure(_ viewModel: FAHomeViewModel) { +// self.contentConfiguration = UIHostingConfiguration(content: { +// FAHomeMustSeeView(viewModel: viewModel) +// }) +// .margins(.all, 0) + + let uiView = FAHomeMustSeeView(viewModel: viewModel) + if let hostingVC = hostingVC { + hostingVC.rootView = uiView + } else { + let hostingVC = UIHostingController(rootView: uiView) + hostingVC.view.backgroundColor = .clear + contentView.addSubview(hostingVC.view) + hostingVC.view.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + self.hostingVC = hostingVC + } + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Fableon/Class/Home/V/FAHomeNewContentCell.swift b/Fableon/Class/Home/V/FAHomeNewContentCell.swift new file mode 100644 index 0000000..975d9f5 --- /dev/null +++ b/Fableon/Class/Home/V/FAHomeNewContentCell.swift @@ -0,0 +1,35 @@ +// +// FAHomeNewContentCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/16. +// + +import UIKit +import SwiftUI + +class FAHomeNewContentCell: UICollectionViewCell { + + private var hostingVC: UIHostingController? + + func configure(_ viewModel: FAHomeViewModel) { +// self.contentConfiguration = UIHostingConfiguration(content: { +// FAHomeNewView(viewModel: viewModel) +// }) +// .margins(.all, 0) + + let uiView = FAHomeNewView(viewModel: viewModel) + if let hostingVC = hostingVC { + hostingVC.rootView = uiView + } else { + let hostingVC = UIHostingController(rootView: uiView) + hostingVC.view.backgroundColor = .clear + contentView.addSubview(hostingVC.view) + hostingVC.view.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + self.hostingVC = hostingVC + } + } +} diff --git a/Fableon/Class/Home/V/FAHomeRecommendedCell.swift b/Fableon/Class/Home/V/FAHomeRecommendedCell.swift new file mode 100644 index 0000000..0e9fcd5 --- /dev/null +++ b/Fableon/Class/Home/V/FAHomeRecommendedCell.swift @@ -0,0 +1,42 @@ +// +// FAHomeRecommendedCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/16. +// + +import UIKit + +class FAHomeRecommendedCell: UICollectionViewCell { + + var model: FAShortPlayModel? { + didSet { + coverImageView.fa_setImage(model?.image_url) + + titleLabel.text = model?.name + + if let text = model?.category?.first, !text.isEmpty { + markView.isHidden = false + markLabel.text = text + } else { + markView.isHidden = true + } + } + } + + @IBOutlet weak var coverImageView: FAImageView! + + @IBOutlet weak var titleLabel: UILabel! + + @IBOutlet weak var markView: UIView! + + @IBOutlet weak var markLabel: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + + self.layer.cornerRadius = 8 + self.layer.masksToBounds = true + } + +} diff --git a/Fableon/Class/Home/V/FAHomeRecommendedCell.xib b/Fableon/Class/Home/V/FAHomeRecommendedCell.xib new file mode 100644 index 0000000..157f658 --- /dev/null +++ b/Fableon/Class/Home/V/FAHomeRecommendedCell.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fableon/Class/Home/V/FAHomeSectionTitleView.swift b/Fableon/Class/Home/V/FAHomeSectionTitleView.swift new file mode 100644 index 0000000..fb8a689 --- /dev/null +++ b/Fableon/Class/Home/V/FAHomeSectionTitleView.swift @@ -0,0 +1,17 @@ +// +// FAHomeSectionTitleView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/16. +// + +import UIKit + +class FAHomeSectionTitleView: UICollectionReusableView { + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + +} diff --git a/Fableon/Class/Home/V/FAHomeSectionTitleView.xib b/Fableon/Class/Home/V/FAHomeSectionTitleView.xib new file mode 100644 index 0000000..50796c9 --- /dev/null +++ b/Fableon/Class/Home/V/FAHomeSectionTitleView.xib @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fableon/Class/Home/V/FASearchHomeView.swift b/Fableon/Class/Home/V/FASearchHomeView.swift new file mode 100644 index 0000000..2d116d4 --- /dev/null +++ b/Fableon/Class/Home/V/FASearchHomeView.swift @@ -0,0 +1,95 @@ +// +// FASearchHomeView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit + +class FASearchHomeView: UIView { + + weak var viewModel: FASearchViewModel? { + didSet { + viewModel?.addObserver(self, forKeyPath: "recommendData", context: nil) + viewModel?.addObserver(self, forKeyPath: "recordList", context: nil) + + self.recommendView.dataArr = self.viewModel?.recommendData ?? [] + self.recordView.dataArr = self.viewModel?.recordList ?? [] + updateLayout() + } + } + + var didSearch: ((_ text: String) -> Void)? + + private lazy var stackView: UIStackView = { + let view = UIStackView() + view.spacing = 20 + view.axis = .vertical + return view + }() + + private lazy var recordView: FASearchRecordView = { + let view = FASearchRecordView() + view.didSearch = { [weak self] text in + self?.didSearch?(text) + } + view.didDelete = { [weak self] in + self?.viewModel?.clearSearchRecord() + } + return view + }() + + private lazy var recommendView: FASearchRecommendView = { + let view = FASearchRecommendView() + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + updateLayout() + + fa_setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "recommendData" { + self.recommendView.dataArr = self.viewModel?.recommendData ?? [] + } else if keyPath == "recordList" { + self.recordView.dataArr = self.viewModel?.recordList ?? [] + } + updateLayout() + } + + func updateLayout() { + stackView.fa_removeAllArrangedSubview() + + if self.recordView.dataArr.count > 0 { + stackView.addArrangedSubview(recordView) + } + if self.recommendView.dataArr.count > 0 { + stackView.addArrangedSubview(self.recommendView) + } + + } + +} + +extension FASearchHomeView { + + private func fa_setupLayout() { + addSubview(stackView) + + stackView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().offset(20) + make.bottom.lessThanOrEqualToSuperview() +// make.bottom.equalToSuperview() + } + } + +} diff --git a/Fableon/Class/Home/V/FASearchInputView.swift b/Fableon/Class/Home/V/FASearchInputView.swift new file mode 100644 index 0000000..6be5e09 --- /dev/null +++ b/Fableon/Class/Home/V/FASearchInputView.swift @@ -0,0 +1,108 @@ +// +// FASearchInputView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit + +class FASearchInputView: UIView { + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: 32) + } + + + + var didSearch: ((_ text: String) -> Void)? + + var text: String? { + get { + return textField.text + } + set { + textField.text = newValue + } + } + + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "Search")) + imageView.setContentHuggingPriority(.required, for: .horizontal) + imageView.setContentCompressionResistancePriority(.required, for: .horizontal) + return imageView + }() + + private lazy var textField: UITextField = { + let textField = UITextField(frame: .zero) + textField.tintColor = UIColor.FFFFFF + textField.delegate = self + textField.returnKeyType = .search + textField.font = .font(ofSize: 12, weight: .medium) + textField.textColor = .FFFFFF + textField.attributedPlaceholder = NSAttributedString(string: "Search".localized, attributes: [ + .font : UIFont.font(ofSize: 12, weight: .medium), + .foregroundColor : UIColor.FFFFFF.withAlphaComponent(0.5) + ]) + return textField + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.layer.cornerRadius = 16 + self.layer.masksToBounds = true + self.backgroundColor = .FFFFFF_0_25 + + fa_setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @discardableResult + override func becomeFirstResponder() -> Bool { + super.becomeFirstResponder() + return self.textField.becomeFirstResponder() + } + + @discardableResult + override func resignFirstResponder() -> Bool { + super.resignFirstResponder() + return self.textField.resignFirstResponder() + } + +} + +extension FASearchInputView { + + private func fa_setupLayout() { + addSubview(iconImageView) + addSubview(textField) + + iconImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(18) + } + + textField.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.left.equalTo(iconImageView.snp.right).offset(6) + make.right.equalToSuperview().offset(-18) + } + } + +} + +//MARK: UITextFieldDelegate +extension FASearchInputView: UITextFieldDelegate { + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + if let text = textField.text { + self.didSearch?(text) + } + return true + } + +} diff --git a/Fableon/Class/Home/V/FASearchRecommendCell.swift b/Fableon/Class/Home/V/FASearchRecommendCell.swift new file mode 100644 index 0000000..92e5172 --- /dev/null +++ b/Fableon/Class/Home/V/FASearchRecommendCell.swift @@ -0,0 +1,55 @@ +// +// FASearchRecommendCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit + +class FASearchRecommendCell: UICollectionViewCell { + + + var model: FAShortPlayModel? { + didSet { + coverImageView.fa_setImage(model?.image_url) + titleLabel.text = model?.name + countLabel.text = "\(model?.watch_total ?? 0)" + epLabel.text = "Ep.##".localizedReplace(text: "\(model?.episode_total ?? 0)") + } + } + + var row: Int = 0 { + didSet { + let num = row + 1 + numberLabel.text = "\(num)" + switch num { + case 1: + numberLabel.textColor = .F_94_F_7_F + case 2: + numberLabel.textColor = .FE_6_C_05 + case 3: + numberLabel.textColor = .F_8_D_01_D + default: + numberLabel.textColor = .FFFFFF.withAlphaComponent(0.8) + } + } + } + + @IBOutlet weak var numberLabel: UILabel! + + @IBOutlet weak var titleLabel: UILabel! + + @IBOutlet weak var coverImageView: FAImageView! + + @IBOutlet weak var countLabel: UILabel! + + @IBOutlet weak var epLabel: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + + + } + +} diff --git a/Fableon/Class/Home/V/FASearchRecommendCell.xib b/Fableon/Class/Home/V/FASearchRecommendCell.xib new file mode 100644 index 0000000..23e123e --- /dev/null +++ b/Fableon/Class/Home/V/FASearchRecommendCell.xib @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fableon/Class/Home/V/FASearchRecommendView.swift b/Fableon/Class/Home/V/FASearchRecommendView.swift new file mode 100644 index 0000000..eef613c --- /dev/null +++ b/Fableon/Class/Home/V/FASearchRecommendView.swift @@ -0,0 +1,99 @@ +// +// FASearchRecommendView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit + +class FASearchRecommendView: UIView { + + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: UIScreen.height) + } + + var dataArr: [FAShortPlayModel] = [] { + didSet { + self.collectionView.reloadData() + } + } + + private lazy var bgView: UIView = { + let view = UIView() + view.backgroundColor = ._5_CA_8_FF_0_2 + view.fa_setRoundedCorner(topLeft: 27, topRight: 27, bottomLeft: 0, bottomRight: 0) + return view + }() + + private lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.itemSize = .init(width: UIScreen.width - 32, height: 70) + layout.minimumInteritemSpacing = 15 + return layout + }() + + private lazy var collectionView: FACollectionView = { + let collectionView = FACollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsVerticalScrollIndicator = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.keyboardDismissMode = .onDrag + collectionView.contentInset = .init(top: 15, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + collectionView.register(UINib(nibName: "FASearchRecommendCell", bundle: nil), forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + fa_setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension FASearchRecommendView { + + private func fa_setupLayout() { + addSubview(bgView) + addSubview(collectionView) + + bgView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview() + make.bottom.equalToSuperview() + make.centerX.equalToSuperview() + } + + collectionView.snp.makeConstraints { make in + make.edges.equalTo(bgView) + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension FASearchRecommendView: UICollectionViewDelegate, UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! FASearchRecommendCell + cell.row = indexPath.row + cell.model = dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = dataArr[indexPath.row] + let vc = FAPlayerDetailViewController() + vc.shortPlayId = model.short_play_id + self.viewController?.navigationController?.pushViewController(vc, animated: true) + } +} diff --git a/Fableon/Class/Home/V/FASearchRecordCell.swift b/Fableon/Class/Home/V/FASearchRecordCell.swift new file mode 100644 index 0000000..cf62f65 --- /dev/null +++ b/Fableon/Class/Home/V/FASearchRecordCell.swift @@ -0,0 +1,26 @@ +// +// FASearchRecordCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit + +class FASearchRecordCell: UICollectionViewCell { + + static let TextFont: UIFont = .font(ofSize: 12, weight: .regular) + + + @IBOutlet weak var textLabel: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + contentView.layer.cornerRadius = 12 + contentView.layer.masksToBounds = true + contentView.backgroundColor = .FFFFFF_0_25 + + textLabel.font = Self.TextFont + } + +} diff --git a/Fableon/Class/Home/V/FASearchRecordCell.xib b/Fableon/Class/Home/V/FASearchRecordCell.xib new file mode 100644 index 0000000..a67160c --- /dev/null +++ b/Fableon/Class/Home/V/FASearchRecordCell.xib @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fableon/Class/Home/V/FASearchRecordView.swift b/Fableon/Class/Home/V/FASearchRecordView.swift new file mode 100644 index 0000000..442a90d --- /dev/null +++ b/Fableon/Class/Home/V/FASearchRecordView.swift @@ -0,0 +1,135 @@ +// +// FASearchRecordView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit +import collection_view_layouts + +class FASearchRecordView: UIView { + + var didSearch: ((_ text: String) -> Void)? + var didDelete: (() -> Void)? + + var dataArr: [String] = [] { + didSet { + self.collectionView.reloadData() + } + } + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .FFFFFF + label.text = "Historical search".localized + return label + }() + + private lazy var deleteButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "路径 264"), for: .normal) + button.addAction(UIAction(handler: { [weak self] _ in + self?.didDelete?() + }), for: .touchUpInside) + return button + }() + + private lazy var collectionViewLayout: TagsLayout = { + let layout = TagsLayout() + layout.delegate = self + layout.contentPadding = ItemsPadding(horizontal: 16, vertical: 0) + layout.cellsPadding = ItemsPadding(horizontal: 12, vertical: 12) + return layout + }() + + private lazy var collectionView: FACollectionView = { + let collectionView = FACollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.addObserver(self, forKeyPath: "contentSize", context: nil) + collectionView.register(UINib(nibName: "FASearchRecordCell", bundle: nil), forCellWithReuseIdentifier: "tagCell") + return collectionView + }() + + deinit { + self.collectionView.removeObserver(self, forKeyPath: "contentSize") + } + + override init(frame: CGRect) { + super.init(frame: frame) + + fa_setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "contentSize" { + let height = self.collectionView.contentSize.height + debugLog(height) + self.collectionView.snp.updateConstraints { make in + make.height.equalTo(height + 1) + } + } + } +} + +extension FASearchRecordView { + + private func fa_setupLayout() { + addSubview(titleLabel) + addSubview(deleteButton) + addSubview(collectionView) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerY.equalTo(deleteButton) + } + + deleteButton.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-16) + make.top.equalToSuperview() + } + + collectionView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(28) + make.left.equalToSuperview() + make.right.equalToSuperview() + make.bottom.equalToSuperview() + make.height.equalTo(1) + } + } + +} + +//MARK: UICollectionViewDataSource UICollectionViewDataSource +extension FASearchRecordView: UICollectionViewDataSource, UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "tagCell", for: indexPath) as! FASearchRecordCell + cell.textLabel.text = dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + self.didSearch?(dataArr[indexPath.row]) + } +} + +//MARK: LayoutDelegate +extension FASearchRecordView: LayoutDelegate { + + func cellSize(indexPath: IndexPath) -> CGSize { + let text = dataArr[indexPath.row] + let size = text.size(FASearchRecordCell.TextFont, .init(width: UIScreen.width, height: 24)) + return .init(width: size.width + 24, height: 24) + } +} diff --git a/Fableon/Class/Home/V/FASearchResultCell.swift b/Fableon/Class/Home/V/FASearchResultCell.swift new file mode 100644 index 0000000..1ab538b --- /dev/null +++ b/Fableon/Class/Home/V/FASearchResultCell.swift @@ -0,0 +1,52 @@ +// +// FASearchResultCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit + +class FASearchResultCell: UICollectionViewCell { + + + static let CellWidth: CGFloat = floor((UIScreen.width - 32 - 13) / 2) + static let CoverHeight: CGFloat = 219 / 164 * CellWidth + static let TitleFont = UIFont.font(ofSize: 14, weight: .medium) + + var model: FAShortPlayModel? { + didSet { + titleLabel.text = model?.name + coverImageView.fa_setImage(model?.image_url) + + if let category = model?.category?.first, !category.isEmpty { + categoryView.isHidden = false + categoryLabel.text = category + } else { + categoryView.isHidden = true + } + } + } + + + + @IBOutlet weak var coverImageView: FAImageView! + + @IBOutlet weak var titleLabel: UILabel! + + @IBOutlet weak var categoryView: UIView! + + @IBOutlet weak var categoryLabel: UILabel! + + @IBOutlet weak var coverHeight: NSLayoutConstraint! + + override func awakeFromNib() { + super.awakeFromNib() + titleLabel.font = Self.TitleFont + + + + coverHeight.constant = Self.CoverHeight + } + +} diff --git a/Fableon/Class/Home/V/FASearchResultCell.xib b/Fableon/Class/Home/V/FASearchResultCell.xib new file mode 100644 index 0000000..56b8219 --- /dev/null +++ b/Fableon/Class/Home/V/FASearchResultCell.xib @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fableon/Class/Home/V/FASearchResultView.swift b/Fableon/Class/Home/V/FASearchResultView.swift new file mode 100644 index 0000000..afaa066 --- /dev/null +++ b/Fableon/Class/Home/V/FASearchResultView.swift @@ -0,0 +1,113 @@ +// +// FASearchResultView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit + +class FASearchResultView: UIView { + + weak var viewModel: FASearchViewModel? + + var dataArr: [FAShortPlayModel] = [] + + private lazy var collectionViewLayout: FAWaterfallFlowLayout = { + let layout = FAWaterfallFlowLayout() + layout.delegate = self + return layout + }() + + private lazy var collectionView: FACollectionView = { + let collectionView = FACollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.ly_emptyView = FAEmpty.fa_emptyView(image: UIImage(named: "__question"), title: "empty_title_01".localized) + collectionView.register(UINib(nibName: "FASearchResultCell", bundle: nil), forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + fa_setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func search(_ text: String) { + if text.isEmpty { + self.dataArr.removeAll() + self.collectionView.reloadData() + return + } + + FAAPI.requestSearch(text: text) { [weak self] list in + guard let self = self else { return } + guard let list = list else { return } + self.dataArr = list + self.collectionView.reloadData() + } + } + +} + +extension FASearchResultView { + + private func fa_setupLayout() { + addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(20) + } + } + +} + + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension FASearchResultView: UICollectionViewDelegate, UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! FASearchResultCell + cell.model = dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = dataArr[indexPath.row] + let vc = FAPlayerDetailViewController() + vc.shortPlayId = model.short_play_id + self.viewController?.navigationController?.pushViewController(vc, animated: true) + } +} + +//MARK: FAWaterfallMutiSectionDelegate +extension FASearchResultView: FAWaterfallMutiSectionDelegate { + + func heightForRowAtIndexPath(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat { + let text = dataArr[indexPath.row].name ?? "" + + return FASearchResultCell.CoverHeight + text.size(FASearchResultCell.TitleFont, .init(width: FASearchResultCell.CellWidth - 24, height: CGFloat(MAXFLOAT))).height + 18 + } + + func interitemSpacing(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGFloat { + return 13 + } + + func lineSpacing(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGFloat { + return 12 + } + + func insetForSection(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> UIEdgeInsets { + return .init(top: 0, left: 16, bottom: 0, right: 16) + } + +} diff --git a/Fableon/Class/Home/VM/FAHomeViewModel.swift b/Fableon/Class/Home/VM/FAHomeViewModel.swift new file mode 100644 index 0000000..ae64c98 --- /dev/null +++ b/Fableon/Class/Home/VM/FAHomeViewModel.swift @@ -0,0 +1,115 @@ +// +// FAHomeViewModel.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/8/26. +// + +import SwiftUI + +@MainActor +class FAHomeViewModel: ObservableObject { + + @Published var dataArr: [FAHomeItem] = [] + + @Published var bannerItem: FAHomeModuleItem? + @Published var mustSeeArr: [FAHomeModuleItem] = [] + @Published var homeNewItem: FAHomeModuleItem? + @Published var recommendedItem: FAHomeModuleItem? + + func requestHomeData(completer: (() -> Void)?) { + FAAPI.requestHomeModulesData { [weak self] list in + guard let self = self else { return } + guard let list = list else { + completer?() + return + } + self.dataArr.removeAll() + + var popularItem: FAHomeModuleItem? + var rankingsItem: FAHomeModuleItem? + var genresItem: FAHomeModuleItem? + var newItem: FAHomeModuleItem? + + list.forEach { + if $0.module_key == .banner { + self.bannerItem = $0 + } else if $0.module_key == .v3_recommand { + $0.title = "Popular".localized + popularItem = $0 + } else if $0.module_key == .week_ranking { + $0.title = "Rankings".localized + rankingsItem = $0 + } else if $0.module_key == .cagetory_recommand, genresItem == nil { + $0.title = "Genres".localized + genresItem = $0 + } else if $0.module_key == .new_recommand { + $0.title = "New".localized + newItem = $0 + self.homeNewItem = $0 + } else if $0.module_key == .week_recommend { + self.recommendedItem = $0 + } + } + + var mustAee: [FAHomeModuleItem] = [] + if let item = popularItem { + mustAee.append(item) + } + if let item = rankingsItem { + mustAee.append(item) + } + if let item = genresItem { + mustAee.append(item) + } + if let item = newItem { + mustAee.append(item) + } + self.mustSeeArr = mustAee + + if let item = self.bannerItem { + let homeItem = FAHomeItem() + homeItem.type = .banner + homeItem.data = item + homeItem.cellHeight = 150 + self.dataArr.append(homeItem) + } + + if self.mustSeeArr.count > 0 { + let homeItem = FAHomeItem() + homeItem.type = .mustSee + homeItem.data = self.mustSeeArr + homeItem.cellHeight = 350 + self.dataArr.append(homeItem) + } + + if let item = self.homeNewItem { + let homeItem = FAHomeItem() + homeItem.type = .new + homeItem.data = item + homeItem.cellHeight = 272 + self.dataArr.append(homeItem) + } + + if let item = self.recommendedItem { + let homeItem = FAHomeItem() + homeItem.type = .recommended + homeItem.data = item + self.dataArr.append(homeItem) + } + completer?() + } + } +} + +extension FAHomeViewModel { + + func pushPlayerDetail(_ model: FAShortPlayModel?) { + guard let model = model else { return } + + let vc = FAPlayerDetailViewController() + vc.shortPlayId = model.short_play_id + FATool.topViewController?.navigationController?.pushViewController(vc, animated: true) + } + +} diff --git a/Fableon/Class/Home/VM/FASearchViewModel.swift b/Fableon/Class/Home/VM/FASearchViewModel.swift new file mode 100644 index 0000000..89451e9 --- /dev/null +++ b/Fableon/Class/Home/VM/FASearchViewModel.swift @@ -0,0 +1,57 @@ +// +// FASearchViewModel.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit + +class FASearchViewModel: NSObject { + static let searchRecordUserDefaultKey = "FASearchViewModel.searchRecordUserDefaultKey" + + @objc dynamic private(set) var recordList: [String] = (UserDefaults.standard.object(forKey: FASearchViewModel.searchRecordUserDefaultKey) as? [String]) ?? [] + + @objc dynamic private(set) lazy var recommendData: [FAShortPlayModel] = [] + + + + func addSearchRecord(text: String) { + guard !text.isEmpty else { return } + var list = recordList + + for (index, value) in list.enumerated() { + if value == text { + list.remove(at: index) + break + } + } + + list.insert(text, at: 0) + + if list.count > 10 { + list.removeLast() + } + recordList = list + + UserDefaults.standard.set(list, forKey: FASearchViewModel.searchRecordUserDefaultKey) + } + + func clearSearchRecord() { + recordList.removeAll() + UserDefaults.standard.set(recordList, forKey: FASearchViewModel.searchRecordUserDefaultKey) + } + + ///获取推荐搜索 + func requestSearchRecommendData(completer: (() -> Void)?) { + FAAPI.requestHotSearchData { [weak self] list in + guard let self = self else { return } + if let list = list { + self.recommendData = list + } + } + } + + + +} diff --git a/Fableon/Class/Me/C/FAAboutViewController.swift b/Fableon/Class/Me/C/FAAboutViewController.swift new file mode 100644 index 0000000..d97f0cd --- /dev/null +++ b/Fableon/Class/Me/C/FAAboutViewController.swift @@ -0,0 +1,106 @@ +// +// FAAboutViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/15. +// + +import UIKit + +class FAAboutViewController: FAViewController { + + + private lazy var dataArr: [FAMeItemModel] = [ + FAMeItemModel(type: .privacyPolicy, name: "Privacy Policy".localized), + FAMeItemModel(type: .userAgreement, name: "User Agreement".localized), + FAMeItemModel(type: .visitWebsite, name: "Visit Website".localized), + ] + + private lazy var tableView: FATableView = { + let tableView = FATableView(frame: .zero, style: .plain) + tableView.tableHeaderView = self.headerView + tableView.delegate = self + tableView.dataSource = self + tableView.separatorInset = .init(top: 0, left: 32, bottom: 0, right: 32) + tableView.register(UINib(nibName: "FAAboutCell", bundle: nil), forCellReuseIdentifier: "cell") + return tableView + }() + + private lazy var headerView: FAAboutHeaderView = { + let view = FAAboutHeaderView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 186)) + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "About".localized + + fa_setupLayout() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.fa_setNavigationStyle() + } + +} + +extension FAAboutViewController { + + private func fa_setupLayout() { + view.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + } + +} + + +//MARK: UITableViewDelegate UITableViewDataSource +extension FAAboutViewController: UITableViewDelegate, UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return dataArr.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FAAboutCell + cell.item = dataArr[indexPath.row] + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let item = dataArr[indexPath.row] + var urlStr: String? = nil + + switch item.type { + case .privacyPolicy: + urlStr = FAWebBaseURL + "/private" + + case .userAgreement: + urlStr = FAWebBaseURL + "/user_policy" + + case .visitWebsite: + if let url = URL(string: FAWebBaseURL) { + UIApplication.shared.open(url) + } + default: + break + } + + if let urlStr = urlStr { + let vc = FABaseWebViewController() + vc.webUrl = urlStr + self.navigationController?.pushViewController(vc, animated: true) + + } + + } + + + +} diff --git a/Fableon/Class/Me/C/FAFeedbackViewController.swift b/Fableon/Class/Me/C/FAFeedbackViewController.swift new file mode 100644 index 0000000..e6534f3 --- /dev/null +++ b/Fableon/Class/Me/C/FAFeedbackViewController.swift @@ -0,0 +1,30 @@ +// +// FAFeedbackViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/16. +// + +import UIKit + +class FAFeedbackViewController: FAAppWebViewController { + + override func viewDidLoad() { + self.webUrl = kFAFeedBackHomeWebUrl + super.viewDidLoad() + + + } + + + /* + // 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. + } + */ + +} diff --git a/Fableon/Class/Me/C/FAMeViewController.swift b/Fableon/Class/Me/C/FAMeViewController.swift new file mode 100644 index 0000000..2f21b59 --- /dev/null +++ b/Fableon/Class/Me/C/FAMeViewController.swift @@ -0,0 +1,188 @@ +// +// FAMeViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/29. +// + +import UIKit +import YYText + +class FAMeViewController: FAViewController { + + + private lazy var dataArr: [FAMeItemModel] = { + let arr = [ +// FAMeItemModel(type: .feedback, name: "Feedback".localized, icon: UIImage(named: "icon_feedback")), + FAMeItemModel(type: .about, name: "About".localized, icon: UIImage(named: "icon_about")), +// FAMeItemModel(type: .setting, name: "Setting".localized, icon: UIImage(named: "icon_setting")) + + FAMeItemModel(type: .privacyPolicy, name: "Privacy Policy".localized, icon: UIImage(named: "icon_privacy")), + FAMeItemModel(type: .userAgreement, name: "User Agreement".localized, icon: UIImage(named: "icon_user")), + FAMeItemModel(type: .visitWebsite, name: "Visit Website".localized, icon: UIImage(named: "icon_visit")), + ] + return arr + }() + + + private lazy var scrollView: FAScrollView = { + let scrollView = FAScrollView() + return scrollView + }() + + private lazy var headerView: FAMeHeaderView = { + let view = FAMeHeaderView() + view.userInfo = FALogin.manager.userInfo + return view + }() + + private lazy var contentView: UIView = { + let view = UIView() + view.backgroundColor = ._4_D_4_A_4_A.withAlphaComponent(0.5) + view.fa_setRoundedCorner(topLeft: 30, topRight: 30, bottomLeft: 0, bottomRight: 0) + return view + }() + + private lazy var tableView: FATableView = { + let tableView = FATableView(frame: .zero, style: .plain) + tableView.delegate = self + tableView.dataSource = self + tableView.rowHeight = 56 + tableView.separatorInset = .init(top: 0, left: 32, bottom: 0, right: 32) + tableView.register(UINib(nibName: "FAMeCell", bundle: nil), forCellReuseIdentifier: "cell") + tableView.addObserver(self, forKeyPath: "contentSize", context: nil) + return tableView + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override func viewDidLoad() { + super.viewDidLoad() + NotificationCenter.default.addObserver(self, selector: #selector(userInfoUpdateNotification), name: FALogin.userInfoUpdateNotification, object: nil) + + fa_setupLayout() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + FALogin.manager.requestUserInfo(completer: nil) + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "contentSize" { + self.updateLayout() + } + } + + + @objc private func userInfoUpdateNotification() { + headerView.userInfo = FALogin.manager.userInfo + } + +} + +extension FAMeViewController { + + private func fa_setupLayout() { + view.addSubview(scrollView) + scrollView.addSubview(headerView) + scrollView.addSubview(contentView) + contentView.addSubview(tableView) + + scrollView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.safeTop) + } + + headerView.snp.makeConstraints { make in + make.left.centerX.equalToSuperview() + make.top.equalToSuperview().offset(40) + } + + contentView.snp.makeConstraints { make in + make.left.centerX.equalToSuperview() + make.top.equalTo(headerView.snp.bottom).offset(24) + make.bottom.equalToSuperview() + make.height.equalTo(UIScreen.height - UIScreen.tabBarHeight - UIScreen.safeTop) + } + + tableView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(19) + } + } + + private func updateLayout() { + let maxHeight = UIScreen.height - UIScreen.tabBarHeight - UIScreen.safeTop + let minHeight = UIScreen.height - UIScreen.tabBarHeight - UIScreen.safeTop - self.headerView.height - 40 - 24 + var height = self.tableView.contentSize.height + if height > maxHeight { + height = maxHeight + } + if height < minHeight { + height = minHeight + } + + contentView.snp.updateConstraints { make in + make.height.equalTo(height) + } + } + +} + +//MARK: UITableViewDelegate, UITableViewDataSource +extension FAMeViewController: UITableViewDelegate, UITableViewDataSource { + + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FAMeCell + cell.item = dataArr[indexPath.row] + return cell + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return dataArr.count + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let item = dataArr[indexPath.row] + switch item.type { + case .about: + let vc = FAAboutViewController() + self.navigationController?.pushViewController(vc, animated: true) + + case .setting: + let vc = FASettingViewController() + self.navigationController?.pushViewController(vc, animated: true) + + case .feedback: + let vc = FAFeedbackViewController() + self.navigationController?.pushViewController(vc, animated: true) + + case .privacyPolicy: + let vc = FABaseWebViewController() + vc.webUrl = FAWebBaseURL + "/private" + self.navigationController?.pushViewController(vc, animated: true) + + case .userAgreement: + let vc = FABaseWebViewController() + vc.webUrl = FAWebBaseURL + "/user_policy" + self.navigationController?.pushViewController(vc, animated: true) + + case .visitWebsite: + if let url = URL(string: FAWebBaseURL) { + UIApplication.shared.open(url) + } + + default: + break + } + } +} diff --git a/Fableon/Class/Me/C/FASettingViewController.swift b/Fableon/Class/Me/C/FASettingViewController.swift new file mode 100644 index 0000000..74d8a8e --- /dev/null +++ b/Fableon/Class/Me/C/FASettingViewController.swift @@ -0,0 +1,26 @@ +// +// FASettingViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/16. +// + +import UIKit + +class FASettingViewController: FAViewController { + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "Settings".localized + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + self.fa_setNavigationStyle() + } + + + +} diff --git a/Fableon/Class/Me/M/FAMeItemModel.swift b/Fableon/Class/Me/M/FAMeItemModel.swift new file mode 100644 index 0000000..81aa17b --- /dev/null +++ b/Fableon/Class/Me/M/FAMeItemModel.swift @@ -0,0 +1,25 @@ +// +// FAMeItemModel.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/29. +// + +import UIKit + +struct FAMeItemModel { + + enum ItemType { + case feedback + case about + case setting + case privacyPolicy + case userAgreement + case visitWebsite + } + + + var type: ItemType? + var name: String? + var icon: UIImage? +} diff --git a/Fableon/Class/Me/V/FAAboutCell.swift b/Fableon/Class/Me/V/FAAboutCell.swift new file mode 100644 index 0000000..3834c30 --- /dev/null +++ b/Fableon/Class/Me/V/FAAboutCell.swift @@ -0,0 +1,33 @@ +// +// FAAboutCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/15. +// + +import UIKit + +class FAAboutCell: FATableViewCell { + + var item: FAMeItemModel? { + didSet { + titleLabel.text = item?.name + } + } + + + @IBOutlet weak var titleLabel: UILabel! + + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + +} diff --git a/Fableon/Class/Me/V/FAAboutCell.xib b/Fableon/Class/Me/V/FAAboutCell.xib new file mode 100644 index 0000000..f309d42 --- /dev/null +++ b/Fableon/Class/Me/V/FAAboutCell.xib @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fableon/Class/Me/V/FAAboutHeaderView.swift b/Fableon/Class/Me/V/FAAboutHeaderView.swift new file mode 100644 index 0000000..3d2adc9 --- /dev/null +++ b/Fableon/Class/Me/V/FAAboutHeaderView.swift @@ -0,0 +1,68 @@ +// +// FAAboutHeaderView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/15. +// + +import UIKit + +class FAAboutHeaderView: UIView { + + private lazy var appLogoView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "logo_image_01")) + imageView.layer.cornerRadius = 8 + imageView.layer.masksToBounds = true + return imageView + }() + + private lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 18, weight: .bold) + label.textColor = .FFFFFF + label.text = kFAAPPName + return label + }() + + private lazy var versionLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = ._999999 + label.text = "Version \(kFAAPPVersion)" + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + fa_setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension FAAboutHeaderView { + private func fa_setupLayout() { + addSubview(appLogoView) + addSubview(nameLabel) + addSubview(versionLabel) + + appLogoView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(30) + make.width.height.equalTo(84) + } + + nameLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(appLogoView.snp.bottom).offset(13) + } + + versionLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(nameLabel.snp.bottom).offset(6) + } + } +} diff --git a/Fableon/Class/Me/V/FAMeCell.swift b/Fableon/Class/Me/V/FAMeCell.swift new file mode 100644 index 0000000..3459770 --- /dev/null +++ b/Fableon/Class/Me/V/FAMeCell.swift @@ -0,0 +1,36 @@ +// +// FAMeCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/29. +// + +import UIKit + +class FAMeCell: FATableViewCell { + + + var item: FAMeItemModel? { + didSet { + iconImageView.image = item?.icon + nameLabel.text = item?.name + } + } + + @IBOutlet weak var iconImageView: UIImageView! + + @IBOutlet weak var nameLabel: UILabel! + + + override func awakeFromNib() { + super.awakeFromNib() + + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + +} diff --git a/Fableon/Class/Me/V/FAMeCell.xib b/Fableon/Class/Me/V/FAMeCell.xib new file mode 100644 index 0000000..0b0e53b --- /dev/null +++ b/Fableon/Class/Me/V/FAMeCell.xib @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fableon/Class/Me/V/FAMeHeaderView.swift b/Fableon/Class/Me/V/FAMeHeaderView.swift new file mode 100644 index 0000000..4514e01 --- /dev/null +++ b/Fableon/Class/Me/V/FAMeHeaderView.swift @@ -0,0 +1,78 @@ +// +// FAMeHeaderView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/29. +// + +import UIKit + +class FAMeHeaderView: UIView { + + var userInfo: FAUserInfo? { + didSet { + avatarImageView.fa_setImage(userInfo?.avator) + + userNameLabel.text = userInfo?.getNickName() + + idLabel.text = "ID:\(userInfo?.customer_id ?? "")" + } + } + + private lazy var avatarImageView: FAImageView = { + let imageView = FAImageView() + imageView.layer.cornerRadius = 33 + return imageView + }() + + private lazy var userNameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 14, weight: .medium) + label.textColor = .FFFFFF + return label + }() + + private lazy var idLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular) + label.textColor = .FFFFFF.withAlphaComponent(0.5) + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + fa_setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension FAMeHeaderView { + + private func fa_setupLayout() { + addSubview(avatarImageView) + addSubview(userNameLabel) + addSubview(idLabel) + + avatarImageView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview() + make.width.height.equalTo(66) + make.bottom.equalToSuperview() + } + + userNameLabel.snp.makeConstraints { make in + make.top.equalTo(avatarImageView).offset(15) + make.left.equalTo(avatarImageView.snp.right).offset(12) + make.right.lessThanOrEqualToSuperview().offset(-16) + } + + idLabel.snp.makeConstraints { make in + make.left.equalTo(userNameLabel) + make.top.equalTo(userNameLabel.snp.bottom).offset(2) + } + } + +} diff --git a/Fableon/Class/MyShort/C/FACollectViewController.swift b/Fableon/Class/MyShort/C/FACollectViewController.swift new file mode 100644 index 0000000..bda7457 --- /dev/null +++ b/Fableon/Class/MyShort/C/FACollectViewController.swift @@ -0,0 +1,193 @@ +// +// FACollectViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/9. +// + +import UIKit +import SnapKit + +class FACollectViewController: FAViewController { + + + private lazy var page = 1 + private lazy var dataArr: [FAShortPlayModel] = [] + + private lazy var fa_isEditing = false { + didSet { + updateBarButton() + self.collectionView.reloadData() + } + } + + + private lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let width = floor((UIScreen.width - 16 - 32) / 3) + let height = 145 / 109 * width + + let layout = UICollectionViewFlowLayout() + layout.itemSize = .init(width: width, height: height + 59) + layout.minimumLineSpacing = 12 + layout.minimumInteritemSpacing = 8 + layout.sectionInset = .init(top: 0, left: 16, bottom: 0, right: 16) + return layout + }() + + private lazy var collectionView: FACollectionView = { + let collectionView = FACollectionView(frame: .zero, collectionViewLayout: self.collectionViewLayout) + collectionView.contentInset = .init(top: 20, left: 0, bottom: 10, right: 0) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.ly_emptyView = FAEmpty.fa_emptyView(image: UIImage(named: "__shop-72"), title: "empty_title_02".localized) + collectionView.register(UINib(nibName: "FACollectCell", bundle: nil), forCellWithReuseIdentifier: "cell") + collectionView.fa_addRefreshHeader(insetTop: collectionView.contentInset.top) { [weak self] in + guard let self = self else { return } + self.handleHeaderRefresh(nil) + } + collectionView.fa_addRefreshFooter(insetBottom: 0) { [weak self] in + self?.handleFooterRefresh(nil) + } + + return collectionView + }() + + private lazy var editBarButton: UIBarButtonItem = { + let item = UIBarButtonItem(image: UIImage(named: "编辑_icon"), style: .plain, target: self, action: #selector(handleEditButton)) + return item + }() + + private lazy var historyButton: UIBarButtonItem = { + let item = UIBarButtonItem(image: UIImage(named: "历史记录_icon"), style: .plain, target: self, action: #selector(handleHistoryButton)) + return item + }() + + private lazy var spaceButton: UIBarButtonItem = { + let item = UIBarButtonItem.fixedSpace(0) + return item + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.edgesForExtendedLayout = .top + self.title = "Collect".localized + + + self.navigationItem.rightBarButtonItems = [historyButton, spaceButton, editBarButton] + + fa_setupLayout() + + requestDataArr(page: 1, completer: nil) + + updateBarButton() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.fa_isEditing = false + } + + private func updateBarButton() { + if fa_isEditing { + editBarButton.image = UIImage(named: "done") + } else { + editBarButton.image = UIImage(named: "编辑_icon") + } + } + + @objc private func handleEditButton() { + fa_isEditing = !fa_isEditing + } + + @objc private func handleHistoryButton() { + + let vc = FAHistoryViewController() + self.navigationController?.pushViewController(vc, animated: true) + } + + override func handleHeaderRefresh(_ completer: (() -> Void)?) { + self.requestDataArr(page: 1) { [weak self] in + self?.collectionView.fa_endHeaderRefreshing() + } + } + + override func handleFooterRefresh(_ completer: (() -> Void)?) { + self.requestDataArr(page: self.page + 1) { [weak self] in + self?.collectionView.fa_endFooterRefreshing() + } + } + +} + +extension FACollectViewController { + + private func fa_setupLayout() { + view.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + } + +} + +//MARK: UICollectionViewDataSource UICollectionViewDelegate +extension FACollectViewController: UICollectionViewDataSource, UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! FACollectCell + cell.model = self.dataArr[indexPath.row] + cell.isEditing = fa_isEditing + cell.clickDeleteButton = { [weak self] cell in + guard let self = self else { return } + guard let indexPath = self.collectionView.indexPath(for: cell) else { return } + guard let shortPlayId = cell.model?.short_play_id else { return } + + FAAPI.requestShortCollect(isCollect: false, shortPlayId: shortPlayId, videoId: cell.model?.short_play_video_id) { [weak self] in + guard let self = self else { return } + self.dataArr.remove(at: indexPath.row) + self.collectionView.deleteItems(at: [indexPath]) + } + } + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = self.dataArr[indexPath.row] + + let vc = FAPlayerDetailViewController() + vc.shortPlayId = model.short_play_id + FATool.topViewController?.navigationController?.pushViewController(vc, animated: true) + } +} + + +extension FACollectViewController { + + private func requestDataArr(page: Int, completer: (() -> Void)?) { + FAAPI.requestCollectList(page: page) { [weak self] listModel in + guard let self = self else { return } + if let list = listModel?.list { + if page == 1 { + self.dataArr.removeAll() + } + self.dataArr += list + self.collectionView.reloadData() + self.page = page + } + completer?() + + self.collectionView.fa_updateNoMoreDataState(listModel?.hasNextPage) + } + } +} diff --git a/Fableon/Class/MyShort/C/FAHistoryViewController.swift b/Fableon/Class/MyShort/C/FAHistoryViewController.swift new file mode 100644 index 0000000..203ec61 --- /dev/null +++ b/Fableon/Class/MyShort/C/FAHistoryViewController.swift @@ -0,0 +1,126 @@ +// +// FAHistoryViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/9. +// + +import UIKit + +class FAHistoryViewController: FAViewController { + + + private lazy var page = 1 + private lazy var dataArr: [FAShortPlayModel] = [] + + private lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.minimumLineSpacing = 12 + layout.itemSize = .init(width: UIScreen.width - 32, height: 115) + return layout + }() + + private lazy var collectionView: FACollectionView = { + let collectionView = FACollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.ly_emptyView = FAEmpty.fa_emptyView(image: UIImage(named: "__shop-72"), title: "empty_title_02".localized) + collectionView.contentInset = .init(top: 20, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + collectionView.register(UINib(nibName: "FAHistoryCell", bundle: nil), forCellWithReuseIdentifier: "cell") + collectionView.fa_addRefreshHeader(insetTop: collectionView.contentInset.top) { [weak self] in + guard let self = self else { return } + self.handleHeaderRefresh(nil) + } + collectionView.fa_addRefreshFooter(insetBottom: 0) { [weak self] in + self?.handleFooterRefresh(nil) + } + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "History".localized + self.edgesForExtendedLayout = .top + + fa_setupLayout() + + self.requestDataArr(page: 1, completer: nil) + } + + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(false, animated: true) + fa_setNavigationStyle() + } + + override func handleHeaderRefresh(_ completer: (() -> Void)?) { + self.requestDataArr(page: 1) { [weak self] in + self?.collectionView.fa_endHeaderRefreshing() + } + } + + override func handleFooterRefresh(_ completer: (() -> Void)?) { + self.requestDataArr(page: self.page + 1) { [weak self] in + self?.collectionView.fa_endFooterRefreshing() + } + } + +} + +extension FAHistoryViewController { + + private func fa_setupLayout() { + view.addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.navBarHeight) + } + } + +} + +//MARK: UICollectionViewDelegate UICollectionViewDataSource +extension FAHistoryViewController: UICollectionViewDelegate, UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! FAHistoryCell + cell.model = dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = dataArr[indexPath.row] + let vc = FAPlayerDetailViewController() + vc.shortPlayId = model.short_play_id + self.navigationController?.pushViewController(vc, animated: true) + } +} + +extension FAHistoryViewController { + + private func requestDataArr(page: Int, completer: (() -> Void)?) { + + FAAPI.requestPlayHistorys(page: page) { [weak self] listModel in + guard let self = self else { return } + + if let list = listModel?.list { + if page == 1 { + self.dataArr.removeAll() + } + self.dataArr += list + self.page = page + self.collectionView.reloadData() + } + completer?() + + self.collectionView.fa_updateNoMoreDataState(listModel?.hasNextPage) + } + } + + +} diff --git a/Fableon/Class/MyShort/V/FACollectCell.swift b/Fableon/Class/MyShort/V/FACollectCell.swift new file mode 100644 index 0000000..77be407 --- /dev/null +++ b/Fableon/Class/MyShort/V/FACollectCell.swift @@ -0,0 +1,51 @@ +// +// FACollectCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/9. +// + +import UIKit + +class FACollectCell: UICollectionViewCell { + + + var clickDeleteButton: ((_ cell: FACollectCell) -> Void)? + + var model: FAShortPlayModel? { + didSet { + nameLabel.text = model?.name + coverImageView.fa_setImage(model?.image_url) + epLabel.text = "Ep.##".localizedReplace(text: "\(model?.episode_total ?? 0)") + } + } + + var isEditing: Bool = true { + didSet { + deleteButton.isHidden = !isEditing + } + } + + + + @IBOutlet weak var coverImageView: FAImageView! + + @IBOutlet weak var nameLabel: UILabel! + + @IBOutlet weak var epLabel: UILabel! + + @IBOutlet weak var deleteButton: UIButton! + + + override func awakeFromNib() { + super.awakeFromNib() + self.deleteButton.layer.borderWidth = 1 + self.deleteButton.layer.borderColor = UIColor.FFFFFF_0_25.cgColor + } + + @IBAction func handleDeleteButton(_ sender: Any) { + self.clickDeleteButton?(self) + } + + +} diff --git a/Fableon/Class/MyShort/V/FACollectCell.xib b/Fableon/Class/MyShort/V/FACollectCell.xib new file mode 100644 index 0000000..987dc4d --- /dev/null +++ b/Fableon/Class/MyShort/V/FACollectCell.xib @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fableon/Class/MyShort/V/FAHistoryCell.swift b/Fableon/Class/MyShort/V/FAHistoryCell.swift new file mode 100644 index 0000000..5dd73f9 --- /dev/null +++ b/Fableon/Class/MyShort/V/FAHistoryCell.swift @@ -0,0 +1,35 @@ +// +// FAHistoryCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/13. +// + +import UIKit + +class FAHistoryCell: UICollectionViewCell { + + var model: FAShortPlayModel? { + didSet { + coverImageView.fa_setImage(model?.image_url) + nameLabel.text = model?.name + epLabel.text = "Ep.##".localizedReplace(text: model?.current_episode) + "/" + "Ep.##".localizedReplace(text: "\(model?.episode_total ?? 0)") + } + } + + + @IBOutlet weak var coverImageView: FAImageView! + + @IBOutlet weak var nameLabel: UILabel! + + + @IBOutlet weak var epLabel: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + self.contentView.layer.cornerRadius = 13 + self.contentView.layer.masksToBounds = true + self.contentView.backgroundColor = ._5_CA_8_FF_0_2 + } + +} diff --git a/Fableon/Class/MyShort/V/FAHistoryCell.xib b/Fableon/Class/MyShort/V/FAHistoryCell.xib new file mode 100644 index 0000000..eb00991 --- /dev/null +++ b/Fableon/Class/MyShort/V/FAHistoryCell.xib @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fableon/Class/Player/M/FAShortDetailModel.swift b/Fableon/Class/Player/M/FAShortDetailModel.swift new file mode 100644 index 0000000..48afe9b --- /dev/null +++ b/Fableon/Class/Player/M/FAShortDetailModel.swift @@ -0,0 +1,23 @@ +// +// FAShortDetailModel.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/8/29. +// + +import UIKit +import SwiftUI +import SmartCodable + + +class FAShortDetailModel: NSObject, Identifiable, SmartCodable { + + var video_info: FAVideoInfoModel? + var shortPlayInfo: FAShortPlayModel? + var episodeList: [FAVideoInfoModel]? + var is_collect: Bool? + var share_coin: Int? + + required override init() { } + +} diff --git a/Fableon/Class/Player/M/FAShortPlayModel.swift b/Fableon/Class/Player/M/FAShortPlayModel.swift new file mode 100644 index 0000000..7078890 --- /dev/null +++ b/Fableon/Class/Player/M/FAShortPlayModel.swift @@ -0,0 +1,39 @@ +// +// FAShortPlayModel.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/8/26. +// + +import UIKit +import SmartCodable + +class FAShortPlayModel: NSObject, Identifiable, SmartCodable { + required override init() { } + + var id: String? + var fa_description: String? + var name: String? + var watch_total: Int? + var current_episode: String? + var image_url: String? + var is_collect: Bool? + var collect_total: Int? + var episode_total: Int? + var horizontally_img: String? + var category: [String]? + var short_play_id: String? + var short_play_video_id: String? + var video_info: FAVideoInfoModel? + + @SmartIgnored + var cellHeight: CGFloat = 0 + + + static func mappingForKey() -> [SmartKeyTransformer]? { + return [ + CodingKeys.fa_description <--- ["description", "short_video_description"], + CodingKeys.name <--- ["short_video_title", "name"] + ] + } +} diff --git a/Fableon/Class/Player/M/FAVideoInfoModel.swift b/Fableon/Class/Player/M/FAVideoInfoModel.swift new file mode 100644 index 0000000..5644c4a --- /dev/null +++ b/Fableon/Class/Player/M/FAVideoInfoModel.swift @@ -0,0 +1,25 @@ +// +// FAVideoInfoModel.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/8/29. +// + +import UIKit +import SmartCodable + +class FAVideoInfoModel: NSObject, Identifiable, SmartCodable { + + required override init() { } + + var short_play_id: String? + var short_play_video_id: String? + var video_url: String? + var episode: String? + var coins: Int? + ///是否锁定,购买后解锁 + var is_lock: Bool? + var image_url: String? + ///播放进度,毫秒 + var play_seconds: Int? +} diff --git a/Fableon/Class/Player/UI/FAPlayerEpUIButton.swift b/Fableon/Class/Player/UI/FAPlayerEpUIButton.swift new file mode 100644 index 0000000..72647c1 --- /dev/null +++ b/Fableon/Class/Player/UI/FAPlayerEpUIButton.swift @@ -0,0 +1,46 @@ +// +// FAPlayerEpUIButton.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/18. +// + +import SwiftUI + +struct FAPlayerEpUIButton: View { + + var text: String? + + var clickHandle: (() -> Void)? + + var body: some View { + + HStack() { + Spacer(minLength: 14) + + HStack { + HStack(spacing: 9) { + Image("Frame 3008") + Text(text ?? "") + .font(Font.font(size: 12, weight: .regular)) + .foregroundStyle(Color.FFFFFF) + } + + Spacer() + Image("Frame 3009") + } + Spacer(minLength: 14) + } + .frame(height: 32) + .frame(maxWidth: .infinity) + .background(Color.init(.color_FFFFFF).opacity(0.2)) + .clipShape(RoundedRectangle(cornerRadius: 16)) + .onTapGesture { + self.clickHandle?() + } + } +} + +#Preview { + FAPlayerEpUIButton() +} diff --git a/Fableon/Class/Player/V/FAEpMenuView.swift b/Fableon/Class/Player/V/FAEpMenuView.swift new file mode 100644 index 0000000..305f227 --- /dev/null +++ b/Fableon/Class/Player/V/FAEpMenuView.swift @@ -0,0 +1,187 @@ +// +// FAEpMenuView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/29. +// + +import UIKit +import YYCategories + +class FAEpMenuView: UIView { + + override var intrinsicContentSize: CGSize { + return CGSize(width: UIScreen.width, height: 35) + } + + var didSelectedIndex: ((_ index: Int) -> Void)? + + var dataArr: [String] = [] { + didSet { + self.reloadData() + } + } + + var selectedIndex: Int = 0 { + didSet { + self.buttonArr.forEach { + $0.isSelected = $0.tag == selectedIndex + if $0.isSelected { + self.updateLinePosition(to: $0, true) + } + } + } + } + + private lazy var buttonArr: [UIButton] = [] + + //MARK: UI属性 + private lazy var scrollView: FAScrollView = { + let scrollView = FAScrollView() + scrollView.showsVerticalScrollIndicator = false + scrollView.showsHorizontalScrollIndicator = false + return scrollView + }() + + private lazy var lineView: UIView = { + let view = UIView() + view.layer.cornerRadius = 2 + view.layer.masksToBounds = true + view.backgroundColor = ._35_A_4_FE + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + fa_setupLayout() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func reloadData() { + buttonArr.forEach { + $0.removeFromSuperview() + } + buttonArr.removeAll() + + let count = self.dataArr.count + + var previousButton: UIButton? + + dataArr.enumerated().forEach { + let text = $1 + let normalStrig = NSMutableAttributedString(string: $1) + normalStrig.yy_color = ._999999 + normalStrig.yy_font = .font(ofSize: 16, weight: .init(900)) + + let selectedString = NSMutableAttributedString(string: $1) + selectedString.yy_color = ._35_A_4_FE + selectedString.yy_font = .font(ofSize: 16, weight: .init(900)) + + + var config = UIButton.Configuration.plain() + config.background.backgroundColor = .clear + config.contentInsets = .zero + + let button = UIButton(configuration: config) + button.tag = $0 + button.configurationUpdateHandler = { button in + let font = UIFont.font(ofSize: 16, weight: .init(900)) + + if button.isSelected { + button.configuration?.attributedTitle = AttributedString(text, attributes: AttributeContainer([ + .font : font, + .foregroundColor : UIColor._35_A_4_FE + ])) + } else { + button.configuration?.attributedTitle = AttributedString(text, attributes: AttributeContainer([ + .font : font, + .foregroundColor : UIColor._999999 + ])) + } + } + + button.addAction(UIAction(handler: { [weak self] _ in + guard let self = self else { return } + self.selectedIndex = button.tag + self.didSelectedIndex?(self.selectedIndex) + }), for: .touchUpInside) + + button.isSelected = $0 == selectedIndex + + self.scrollView.addSubview(button) + self.buttonArr.append(button) + + if previousButton == nil { + button.snp.makeConstraints { make in + make.left.equalToSuperview().offset(30) + make.top.equalToSuperview() + make.height.equalTo(35) + } + } else if let previousButton = previousButton, count - 1 == $0 { + button.snp.makeConstraints { make in + make.top.equalToSuperview() + make.left.equalTo(previousButton.snp.right).offset(40) + make.height.equalTo(35) + make.right.equalToSuperview().offset(-30) + } + } else if let previousButton = previousButton { + button.snp.makeConstraints { make in + make.top.equalToSuperview() + make.left.equalTo(previousButton.snp.right).offset(40) + make.height.equalTo(35) + } + } + + if button.isSelected { + self.updateLinePosition(to: button, false) + } + + previousButton = button + } + + } + + + + @objc private func handleButton(sender: UIButton) { + self.selectedIndex = sender.tag + self.didSelectedIndex?(self.selectedIndex) + } + + private func updateLinePosition(to button: UIButton, _ isAnimate: Bool) { + + lineView.snp.remakeConstraints { make in + make.bottom.equalTo(button) + make.width.equalTo(10) + make.height.equalTo(4) + make.centerX.equalTo(button) + } + + if isAnimate { + UIView.animate(withDuration: 0.3) { + self.layoutIfNeeded() + } + } + + } + +} + +extension FAEpMenuView { + + private func fa_setupLayout() { + addSubview(scrollView) + scrollView.addSubview(lineView) + + scrollView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalToSuperview() + } + + + } + +} diff --git a/Fableon/Class/Player/V/FAEpSelectorCell.swift b/Fableon/Class/Player/V/FAEpSelectorCell.swift new file mode 100644 index 0000000..63fd8fa --- /dev/null +++ b/Fableon/Class/Player/V/FAEpSelectorCell.swift @@ -0,0 +1,41 @@ +// +// FAEpSelectorCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/29. +// + +import UIKit + +class FAEpSelectorCell: UICollectionViewCell { + + var model: FAVideoInfoModel? { + didSet { + numberLabel.text = model?.episode + } + } + + var fa_isSelected: Bool = false { + didSet { + if fa_isSelected { + numberLabel.textColor = ._35_A_4_FE + self.contentView.backgroundColor = .FFFFFF + self.contentView.layer.borderColor = UIColor._35_A_4_FE.cgColor + } else { + numberLabel.textColor = .FFFFFF.withAlphaComponent(0.8) + self.contentView.backgroundColor = .FFFFFF.withAlphaComponent(0.25) + self.contentView.layer.borderColor = UIColor.clear.cgColor + } + } + } + + @IBOutlet weak var numberLabel: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + self.layer.masksToBounds = false + self.contentView.layer.cornerRadius = 3 + self.contentView.layer.borderWidth = 1 + } + +} diff --git a/Fableon/Class/Player/V/FAEpSelectorCell.xib b/Fableon/Class/Player/V/FAEpSelectorCell.xib new file mode 100644 index 0000000..67bc00c --- /dev/null +++ b/Fableon/Class/Player/V/FAEpSelectorCell.xib @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fableon/Class/Player/V/FAEpSelectorView.swift b/Fableon/Class/Player/V/FAEpSelectorView.swift new file mode 100644 index 0000000..63b2198 --- /dev/null +++ b/Fableon/Class/Player/V/FAEpSelectorView.swift @@ -0,0 +1,259 @@ +// +// FAEpSelectorView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/23. +// + +import UIKit +import HWPanModal + +class FAEpSelectorView: FAPanModalContentView { + + var didSelected: ((_ index: Int) -> Void)? + + var model: FAShortDetailModel? { + didSet { + titleLabel.text = model?.shortPlayInfo?.name + + collectionView.reloadData() + + let epList = model?.episodeList ?? [] + + var menuDataArr = [String]() + let totalEpisode = epList.count + var index = 0 + var remainingEpisodes = totalEpisode + + while remainingEpisodes > 0 { + let minIndex = index * 24 + var maxIndex = minIndex + 23 + if maxIndex >= epList.count { + maxIndex = epList.count - 1 + } + + let minEpisode = epList[minIndex].episode ?? "0" + let maxEpisode = epList[maxIndex].episode ?? "0" + + if minEpisode == maxEpisode { + menuDataArr.append("\(minEpisode)") + } else { + menuDataArr.append("\(minEpisode)-\(maxEpisode)") + } + + remainingEpisodes -= 24 + index += 1 + } + + self.menuView.dataArr = menuDataArr + } + } + + var selectedIndex: Int = 0 { + didSet { + collectionView.reloadData() + } + } + + private var isDecelerating = false + private var isDragging = false + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .medium) + label.textColor = .FFFFFF + return label + }() + + private lazy var menuView: FAEpMenuView = { + let view = FAEpMenuView() + view.didSelectedIndex = { [weak self] index in + guard let self = self else { return } + let epList = self.model?.episodeList ?? [] + var row = 0 + if index > 0 { + row = index * 24 + 10 + let count = epList.count + if row >= count { + row = count - 1 + } + } + let indexPath = IndexPath.init(row: row, section: 0) + self.collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true) + } + return view + }() + + private lazy var cvLayout: FAWaterfallFlowLayout = { + let layout = FAWaterfallFlowLayout() + layout.delegate = self + return layout + }() + + private lazy var collectionView: FACollectionView = { + let collectionView = FACollectionView(frame: .zero, collectionViewLayout: cvLayout) + collectionView.contentInset = .init(top: 10, left: 0, bottom: UIScreen.safeBottom + 10, right: 0) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.register(UINib(nibName: "FAEpSelectorCell", bundle: nil), forCellWithReuseIdentifier: "cell") + return collectionView + }() + + + + + override init(frame: CGRect) { + super.init(frame: frame) + self.backgroundColor = .clear + self.contentHeight = 350 + UIScreen.safeBottom + + fa_setupLayout() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func backgroundConfig() -> HWBackgroundConfig { + let config = HWBackgroundConfig() + config.backgroundAlpha = 0.6 + return config + } + + override func layoutSubviews() { + super.layoutSubviews() + + + } + + override func present(in view: UIView?) { + super.present(in: view) + self.hw_contentView.fa_addEffectView(style: .dark) + let r = self.cornerRadius() + self.hw_contentView.fa_setRoundedCorner(topLeft: r, topRight: r, bottomLeft: 0, bottomRight: 0) + } + +} + + +extension FAEpSelectorView { + + private func fa_setupLayout() { + addSubview(titleLabel) + addSubview(menuView) + addSubview(collectionView) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(20) + make.right.lessThanOrEqualToSuperview().offset(-20) + make.top.equalToSuperview().offset(18) + } + + menuView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().offset(55) + } + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(107) + } + } + +} + +//MARK: UICollectionViewDelegate, UICollectionViewDataSource +extension FAEpSelectorView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! FAEpSelectorCell + cell.model = model?.episodeList?[indexPath.row] + cell.fa_isSelected = selectedIndex == indexPath.row + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + model?.episodeList?.count ?? 0 + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if self.selectedIndex == indexPath.row { return } + self.selectedIndex = indexPath.row + + self.didSelected?(indexPath.row) + self.dismiss(animated: true) { + + } + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if isDragging || isDecelerating { + updateMuneSelectedIndex() + } + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + isDecelerating = false + updateMuneSelectedIndex() + } + + func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { + isDecelerating = true + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + isDragging = true + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + isDragging = false + } + + func updateMuneSelectedIndex() { + let epList = model?.episodeList ?? [] + let indexPathArr = collectionView.indexPathsForVisibleItems + + var minRow = epList.count - 1 + var maxRow = 0 + + for indexPath in indexPathArr { + if indexPath.row < minRow { + minRow = indexPath.row + } + if indexPath.row > maxRow { + maxRow = indexPath.row + } + } + + let selectedIndex = maxRow / 24 + if menuView.selectedIndex != selectedIndex { + menuView.selectedIndex = selectedIndex + } + } + +} + +//MARK: FAWaterfallMutiSectionDelegate +extension FAEpSelectorView: FAWaterfallMutiSectionDelegate { + + func heightForRowAtIndexPath(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat { + return 44 + } + + func columnNumber(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> Int { + return 6 + } + + func lineSpacing(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGFloat { + return 15 + } + + func interitemSpacing(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGFloat { + return 15 + } + + func insetForSection(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> UIEdgeInsets { + return .init(top: 0, left: 20, bottom: 0, right: 20) + } + +} diff --git a/Fableon/Class/Player/V/FAPlayerDetailCell.swift b/Fableon/Class/Player/V/FAPlayerDetailCell.swift new file mode 100644 index 0000000..4fb60d5 --- /dev/null +++ b/Fableon/Class/Player/V/FAPlayerDetailCell.swift @@ -0,0 +1,42 @@ +// +// FAPlayerDetailCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/1. +// + +import UIKit +import JXPlayer + +class FAPlayerDetailCell: JXPlayerListCell { + + override var ControlViewClass: JXPlayerListControlView.Type { + return FAPlayerDetailControlView.self + } + + + override var model: Any? { + didSet { + let model = self.model as? FAVideoInfoModel + self.player.setPlayUrl(url: model?.video_url ?? "") + } + } + + var shortModel: FAShortPlayModel? { + didSet { + let controlView = self.controlView as? FAPlayerDetailControlView + controlView?.shortModel = shortModel + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} diff --git a/Fableon/Class/Player/V/FAPlayerDetailControlView.swift b/Fableon/Class/Player/V/FAPlayerDetailControlView.swift new file mode 100644 index 0000000..21bf133 --- /dev/null +++ b/Fableon/Class/Player/V/FAPlayerDetailControlView.swift @@ -0,0 +1,235 @@ +// +// FAPlayerDetailControlView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/16. +// + +import UIKit +import JXPlayer +import SwiftUI + +class FAPlayerDetailControlView: JXPlayerListControlView { + + + override var viewModel: JXPlayerListViewModel? { + didSet { + self.viewModel?.addObserver(self, forKeyPath: "isPlaying", context: nil) + } + } + + var fa_viewModel: FAShortDetailViewModel? { + return self.viewModel as? FAShortDetailViewModel + } + + override var model: Any? { + didSet { +// let model = self.model as? FAVideoInfoModel + + updateEp() + } + } + + var shortModel: FAShortPlayModel? { + didSet { + updateEp() + shortNameLabel.text = shortModel?.name + textLabel.text = shortModel?.fa_description + + collectButton.isSelected = shortModel?.is_collect == true + } + } + + override var durationTime: TimeInterval { + didSet { + updateProgress() + } + } + + override var currentTime: TimeInterval { + didSet { + updateProgress() + } + } + + override var isCurrent: Bool { + didSet { + playButton.setNeedsUpdateConfiguration() + } + } + + private lazy var epButton: UIHostingController = { + let view = FAPlayerEpUIButton() + let hc = UIHostingController(rootView: view) + hc.view.backgroundColor = .clear + return hc + }() + + private lazy var progressView: FAPlayerProgressView = { + let view = FAPlayerProgressView() + view.insets = .init(top: 10, left: 16, bottom: 10, right: 16) + view.panFinish = { [weak self] progress in + guard let self = self else { return } + self.viewModel?.seekTo(Float(progress)) + } + return view + }() + + private lazy var textLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular); + label.textColor = UIColor(named: .color_FFFFFF)!.withAlphaComponent(0.8) + label.numberOfLines = 2 + return label + }() + + private lazy var shortNameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .medium) + label.textColor = UIColor(named: .color_FFFFFF) + return label + }() + + private lazy var playButton: UIButton = { + let config = UIButton.Configuration.plain() + + let button = UIButton(configuration: config, primaryAction: UIAction(handler: { [weak self] _ in + self?.fa_viewModel?.userSwitchPlayAndPause() + })) + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + + if self.viewModel?.isPlaying == true || !isCurrent { + button.configuration?.image = UIImage(named: "pause_icon") + } else { + button.configuration?.image = UIImage(named: "play_icon_01") + } + } + return button + }() + + private lazy var collectButton: UIButton = { + var config = UIButton.Configuration.plain() + config.background.backgroundColor = .clear + let button = UIButton(configuration: config) + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + if button.isSelected { + button.configuration?.image = UIImage(named: "collect_star_icon_selected") + } else { + button.configuration?.image = UIImage(named: "collect_star_icon") + } + } + button.addAction(UIAction(handler: { [weak self] _ in + guard let self = self else { return } + guard let shortPlayId = self.shortModel?.short_play_id else { return } + let videoId = (self.model as? FAVideoInfoModel)?.short_play_video_id + let isCollect = !(self.shortModel?.is_collect ?? false) + + FAAPI.requestShortCollect(isCollect: isCollect, shortPlayId: shortPlayId, videoId: videoId, success: nil) + + }), for: .touchUpInside) + + return button + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override init(frame: CGRect) { + super.init(frame: frame) + NotificationCenter.default.addObserver(self, selector: #selector(updateShortCollectStateNotification), name: FAAPI.updateShortCollectStateNotification, object: nil) + + fa_setupLayout() + } + + @MainActor required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "isPlaying" { + playButton.setNeedsUpdateConfiguration() + } + } + + private func updateEp() { + let model = self.model as? FAVideoInfoModel + + let text = "Ep.##".localizedReplace(text: model?.episode ?? "") + "/" + "Ep.##".localizedReplace(text: "\(shortModel?.episode_total ?? 0)") + var view = FAPlayerEpUIButton(text: text) + view.clickHandle = { [weak self] in + self?.fa_viewModel?.onEpSelectorView() + } + epButton.rootView = view + } + + private func updateProgress() { + guard durationTime > 0 else { + progressView.progress = 0 + return + } + progressView.progress = currentTime / durationTime + } + + @objc private func updateShortCollectStateNotification(sender: Notification) { + guard let userInfo = sender.userInfo else { return } + guard let shortPlayId = userInfo["id"] as? String else { return } + guard let state = userInfo["state"] as? Bool else { return } + guard shortPlayId == self.shortModel?.short_play_id else { return } + self.shortModel?.is_collect = state + + collectButton.isSelected = state + } +} + +extension FAPlayerDetailControlView { + + private func fa_setupLayout() { + + addSubview(epButton.view) + addSubview(progressView) + addSubview(textLabel) + addSubview(shortNameLabel) + addSubview(playButton) + addSubview(collectButton) + + + epButton.view.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().offset(-(UIScreen.safeBottom + 10)) + } + + progressView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.centerX.equalToSuperview() + make.bottom.equalTo(epButton.view.snp.top).offset(-8) + } + + textLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.right.lessThanOrEqualToSuperview().offset(-84) + make.bottom.equalTo(progressView.snp.top).offset(1) + } + + shortNameLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.bottom.equalTo(textLabel.snp.top).offset(-5) + make.right.lessThanOrEqualToSuperview().offset(-84) + } + + playButton.snp.makeConstraints { make in + make.center.equalToSuperview() + } + + collectButton.snp.makeConstraints { make in + make.top.equalToSuperview().offset(UIScreen.safeTop) + make.right.equalToSuperview().offset(-16) + make.height.equalTo(44) + } + + } + +} diff --git a/Fableon/Class/Player/V/FAPlayerProgressView.swift b/Fableon/Class/Player/V/FAPlayerProgressView.swift new file mode 100644 index 0000000..372fdad --- /dev/null +++ b/Fableon/Class/Player/V/FAPlayerProgressView.swift @@ -0,0 +1,211 @@ +// +// FAPlayerProgressView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/23. +// + +import UIKit +import YYText + +class FAPlayerProgressView: UIView { + + ///滑动开始 + var panStart: (() -> Void)? + + ///滑动中 + var panChange: ((_ progress: CGFloat) -> Void)? + + ///滑动完成回调 + var panFinish: ((_ progress: CGFloat) -> Void)? + + var progress: CGFloat = 0 { + didSet { + if !isPaning { + setNeedsDisplay() + } + } + } + + ///用来记录滑动时的当前进度 + private var tempProgress: CGFloat = 0 + + ///滑动进度 + private var panProgress: CGFloat = 0 + + var progressColor = UIColor(named: .color_FFFFFF)!.withAlphaComponent(0.2) + var currentProgress = UIColor(named: .color_FFFFFF)! + + var lineWidth: CGFloat = 3 + + ///加载中状态 + var isLoading = false { + didSet { + if isLoading { + if gradientTimer == nil { + gradientTimer = Timer.scheduledTimer(timeInterval: 0.05, target: YYTextWeakProxy(target: self), selector: #selector(handleGradientTimer), userInfo: nil, repeats: true) + } + } else { + gradientTimer?.invalidate() + gradientTimer = nil + } + } + } + + var insets: UIEdgeInsets = .init(top: 0, left: 16, bottom: 0, right: 16) { + didSet { + self.invalidateIntrinsicContentSize() + setNeedsDisplay() + } + } + + private(set) lazy var panGesture: UIPanGestureRecognizer = { + let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(sender:))) + return pan + }() + + private(set) lazy var tagGesture: UITapGestureRecognizer = { + let tap = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(sender:))) + return tap + }() + + ///是否在滑动中 + private var isPaning: Bool = false + + private var gradientTimer: Timer? + + private var gradientValue: CGFloat = 0 + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: lineWidth + insets.top + insets.bottom) + } + + override init(frame: CGRect) { + super.init(frame: frame) +// self.backgroundColor = progressColor + self.backgroundColor = .clear + + self.addGestureRecognizer(panGesture) + self.addGestureRecognizer(tagGesture) + + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + setNeedsDisplay() + } + + @objc private func handleGradientTimer() { + gradientValue += 0.1 + if gradientValue > 1 { + gradientValue = 0 + } + setNeedsDisplay() + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + guard let context = UIGraphicsGetCurrentContext() else { return } + let width = rect.width + + let progressX = insets.left + let progressY = insets.top + let progressWidth = width - insets.left - insets.right + + if isLoading, !isPaning { + // 定义颜色空间 + let colorSpace = CGColorSpaceCreateDeviceRGB() + let colors: [CGColor] = [ + UIColor.clear.cgColor, + UIColor.white.cgColor, + UIColor.clear.cgColor + ] + let locations: [CGFloat] = [0.0, gradientValue, 1.0] + + guard let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: locations) else { + return + } + + let gradientRect = CGRect(x: progressX, + y: progressY, + width: progressWidth, + height: lineWidth) + + // 定义渐变的起点和终点 + let startPoint = CGPoint(x: rect.minX, y: rect.minY) + let endPoint = CGPoint(x: rect.maxX, y: rect.maxY) + + // 裁剪到渐变区域 + context.saveGState() + context.clip(to: gradientRect) + + // 绘制渐变 + context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: []) + } else { + var progress = self.progress + if self.isPaning { + progress = self.panProgress + } + + + ///绘制进度 + let progressPath = UIBezierPath(roundedRect: CGRect(x: progressX, y: progressY, width: progressWidth, height: lineWidth), cornerRadius: lineWidth / 2) + context.addPath(progressPath.cgPath) + context.setFillColor(progressColor.cgColor) + context.fillPath() + + ///绘制当前进度 + let currentPath = UIBezierPath(roundedRect: CGRect(x: progressX, y: progressY, width: progressWidth * progress, height: lineWidth), cornerRadius: lineWidth / 2) + context.addPath(currentPath.cgPath) + context.setFillColor(currentProgress.cgColor) + context.fillPath() + } + + + } + +} + + +extension FAPlayerProgressView { + + @objc func handlePanGesture(sender: UIPanGestureRecognizer) { + + switch sender.state { + case .began: + self.isPaning = true + self.tempProgress = self.progress + sender.setTranslation(CGPoint(x: 0, y: 0), in: self) + self.panStart?() + + case .changed: + let point = sender.translation(in: self) + let offsetX = point.x / (self.width - self.insets.left - self.insets.right) + self.panProgress = self.tempProgress + offsetX + if self.panProgress < 0 { + self.panProgress = 0 + } + self.panChange?(self.panProgress) + setNeedsDisplay() + + default: + self.isPaning = false + self.panFinish?(self.panProgress) + + self.panProgress = 0 + } + } + + @objc func handleTapGesture(sender: UITapGestureRecognizer) { + let point = sender.location(in: self) + let offsetX = (point.x - self.insets.left) / (self.width - self.insets.left - self.insets.right) + self.panFinish?(offsetX) + } + +} diff --git a/Fableon/Class/Player/VC/FAPlayerDetailViewController.swift b/Fableon/Class/Player/VC/FAPlayerDetailViewController.swift new file mode 100644 index 0000000..7a1d05f --- /dev/null +++ b/Fableon/Class/Player/VC/FAPlayerDetailViewController.swift @@ -0,0 +1,150 @@ +// +// FAPlayerDetailViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/1. +// + +import UIKit +import JXPlayer +import FDFullscreenPopGesture + + +class FAPlayerDetailViewController: JXPlayerListViewController { + + var shortPlayId: String? + + override var ViewModelClass: JXPlayerListViewModel.Type { + return FAShortDetailViewModel.self + } + + var fa_viewModel: FAShortDetailViewModel { + return self.viewModel as! FAShortDetailViewModel + } + + private lazy var returnButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "Frame 3011"), for: .normal) + button.addAction(UIAction(handler: { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + }), for: .touchUpInside) + return button + }() + + private lazy var epLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 18, weight: .regular) + label.textColor = .init(named: .color_FFFFFF) + return label + }() + + + override func viewDidLoad() { + super.viewDidLoad() + self.fd_interactivePopDisabled = true + view.backgroundColor = .init(named: .color_000000) + self.fa_viewModel.shortPlayId = shortPlayId ?? "" + self.register(FAPlayerDetailCell.self, forCellWithReuseIdentifier: "FAPlayerDetailCell") + self.delegate = self + self.dataSource = self + + + requestDetailList() + + fa_setupLayout() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.viewModel.currentCell?.pause() + } + + override var previousVideoUrl: String? { + return self.fa_viewModel.previousEpisode?.video_url + } + + override var nextVideoUrl: String? { + return self.fa_viewModel.nextEpisode?.video_url + } + + override func play() { + super.play() + + let videoInfo = self.viewModel.currentCell?.model as? FAVideoInfoModel + FAAPI.requestCreatePlayHistory(videoId: videoInfo?.short_play_video_id, shortPlayId: videoInfo?.short_play_id) + } +} + +extension FAPlayerDetailViewController { + + private func fa_setupLayout() { + view.addSubview(returnButton) + view.addSubview(epLabel) + + returnButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.top.equalToSuperview().offset(UIScreen.safeTop) + make.height.equalTo(44) + } + + epLabel.snp.makeConstraints { make in + make.centerY.equalTo(returnButton) + make.left.equalTo(returnButton.snp.right).offset(4) + } + } + +} + +//MARK: JXPlayerListViewControllerDelegate JXPlayerListViewControllerDataSource +extension FAPlayerDetailViewController: JXPlayerListViewControllerDelegate, JXPlayerListViewControllerDataSource { + func jx_playerListViewController(_ viewController: JXPlayerListViewController, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = self.dequeueReusableCell(withReuseIdentifier: "FAPlayerDetailCell", for: indexPath) as! FAPlayerDetailCell + cell.model = self.fa_viewModel.dataArr[indexPath.section].episodeList?[indexPath.row] + cell.shortModel = self.fa_viewModel.dataArr[indexPath.section].shortPlayInfo + return cell + } + + func jx_playerListViewController(_ viewController: JXPlayerListViewController, numberOfItemsInSection section: Int) -> Int { + self.fa_viewModel.dataArr[section].episodeList?.count ?? 0 + } + + func jx_numberOfSections(in viewController: JXPlayerListViewController) -> Int { + self.fa_viewModel.dataArr.count + } + + func jx_playerListViewController(_ viewController: JXPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath) { + let model = self.fa_viewModel.dataArr[indexPath.section].episodeList?[indexPath.row] + epLabel.text = "Ep.\(model?.episode ?? "")" + } + + func jx_shouldAutoScrollNextEpisode(_ viewController: JXPlayerListViewController) -> Bool { + if let _ = self.fa_viewModel.popView { + return false + } else { + return true + } + } + +} + + +extension FAPlayerDetailViewController { + + private func requestDetailList() { + self.fa_viewModel.requestDetailData { [weak self] code in + guard let self = self else { return } + + + } + } +} + diff --git a/Fableon/Class/Player/VM/FAShortDetailViewModel.swift b/Fableon/Class/Player/VM/FAShortDetailViewModel.swift new file mode 100644 index 0000000..0273f5e --- /dev/null +++ b/Fableon/Class/Player/VM/FAShortDetailViewModel.swift @@ -0,0 +1,72 @@ +// +// Untitled.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/8/29. +// + +import SwiftUI +import JXPlayer + +//@MainActor +class FAShortDetailViewModel: JXPlayerListViewModel, ObservableObject { + + private(set) var dataArr: [FAShortDetailModel] = [] + + var shortPlayId: String = "" + + + + var previousEpisode: FAVideoInfoModel? { + guard dataArr.count > 0 else { return nil } + let detailModel = dataArr[self.currentIndexPath.section] + let row = self.currentIndexPath.row - 1 + if row < 0 { return nil } + return detailModel.episodeList?[row] + } + + var nextEpisode: FAVideoInfoModel? { + guard dataArr.count > 0 else { return nil } + let detailModel = dataArr[self.currentIndexPath.section] + let row = self.currentIndexPath.row + 1 + if row >= (detailModel.episodeList?.count ?? 0) { return nil } + + return detailModel.episodeList?[row] + } + + weak var popView: UIView? + + + func requestDetailData(completer: ((_ code: Int) -> Void)?) { + FAAPI.requestShortDetailData(shortPlayId: shortPlayId) { [weak self] model, code, msg in + guard let self = self else { return } + if let model = model { + self.dataArr.removeAll() + self.dataArr.append(model) + self.playerListVC?.reloadData { + self.playerListVC?.play() + } + } + completer?(code ?? -1) + } + } + +} + +extension FAShortDetailViewModel { + + func onEpSelectorView() { + let view = FAEpSelectorView() + view.selectedIndex = self.currentIndexPath.row + view.model = self.dataArr[currentIndexPath.section] + view.didSelected = { [weak self] index in + guard let self = self else { return } + self.playerListVC?.scrollToItem(indexPath: IndexPath(row: index, section: currentIndexPath.section), animated: false) + } + view.present(in: nil) + + self.popView = view + } + + +} diff --git a/Fableon/Class/Recommend/C/FARecommendViewController.swift b/Fableon/Class/Recommend/C/FARecommendViewController.swift new file mode 100644 index 0000000..6edd85b --- /dev/null +++ b/Fableon/Class/Recommend/C/FARecommendViewController.swift @@ -0,0 +1,73 @@ +// +// FARecommendViewController.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/30. +// + +import UIKit +import JXPlayer + +class FARecommendViewController: JXPlayerListViewController { + + override var contentSize: CGSize { + return .init(width: UIScreen.width, height: UIScreen.height - UIScreen.tabBarHeight) + } + + override var ViewModelClass: JXPlayerListViewModel.Type { + return FARecommendViewModel.self + } + + var fa_viewModel: FARecommendViewModel { + return self.viewModel as! FARecommendViewModel + } + + override func viewDidLoad() { + super.viewDidLoad() + self.register(FARecommendPlayerCell.self, forCellWithReuseIdentifier: "cell") + + self.delegate = self + self.dataSource = self + + self.fa_viewModel.requestDataArr(page: 1) + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.viewModel.currentCell?.pause() + } + + override var previousVideoUrl: String? { + return self.fa_viewModel.previousEpisode?.video_url + } + + override var nextVideoUrl: String? { + return self.fa_viewModel.nextEpisode?.video_url + } + +} + +//MARK: JXPlayerListViewControllerDataSource +extension FARecommendViewController: JXPlayerListViewControllerDataSource { + func jx_playerListViewController(_ viewController: JXPlayerListViewController, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = self.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! FARecommendPlayerCell + cell.model = fa_viewModel.dataArr[indexPath.row] + return cell + } + + func jx_playerListViewController(_ viewController: JXPlayerListViewController, numberOfItemsInSection section: Int) -> Int { + return fa_viewModel.dataArr.count + } + +} + +//MARK: JXPlayerListViewControllerDelegate +extension FARecommendViewController: JXPlayerListViewControllerDelegate { + +} diff --git a/Fableon/Class/Recommend/V/FARecommendPlayerCell.swift b/Fableon/Class/Recommend/V/FARecommendPlayerCell.swift new file mode 100644 index 0000000..cc89048 --- /dev/null +++ b/Fableon/Class/Recommend/V/FARecommendPlayerCell.swift @@ -0,0 +1,35 @@ +// +// FARecommendPlayerCell.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/30. +// + +import UIKit +import JXPlayer + +class FARecommendPlayerCell: JXPlayerListCell { + + override var ControlViewClass: JXPlayerListControlView.Type { + return FARecommendPlayerControlView.self + } + + + override var model: Any? { + didSet { + let model = self.model as? FAShortPlayModel + let videoInfo = model?.video_info + + self.player.setPlayUrl(url: videoInfo?.video_url ?? "") + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Fableon/Class/Recommend/V/FARecommendPlayerControlView.swift b/Fableon/Class/Recommend/V/FARecommendPlayerControlView.swift new file mode 100644 index 0000000..95fc5be --- /dev/null +++ b/Fableon/Class/Recommend/V/FARecommendPlayerControlView.swift @@ -0,0 +1,237 @@ +// +// FARecommendPlayerControlView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/9. +// + +import UIKit +import JXPlayer +import SwiftUI + +class FARecommendPlayerControlView: JXPlayerListControlView { + + override var viewModel: JXPlayerListViewModel? { + didSet { + self.viewModel?.addObserver(self, forKeyPath: "isPlaying", context: nil) + } + } + + var fa_viewModel: FARecommendViewModel? { + return self.viewModel as? FARecommendViewModel + } + + override var model: Any? { + didSet { + let shortModel = self.model as? FAShortPlayModel +// let videoInfo = shortModel?.video_info + + updateEp() + shortNameLabel.text = shortModel?.name + textLabel.text = shortModel?.fa_description + + collectButton.isSelected = shortModel?.is_collect == true + + } + } + + var shortModel: FAShortPlayModel? { + return self.model as? FAShortPlayModel + } + + override var durationTime: TimeInterval { + didSet { + updateProgress() + } + } + + override var currentTime: TimeInterval { + didSet { + updateProgress() + } + } + + override var isCurrent: Bool { + didSet { + playButton.setNeedsUpdateConfiguration() + } + } + + private lazy var epButton: UIHostingController = { + let view = FAPlayerEpUIButton() + let hc = UIHostingController(rootView: view) + hc.view.backgroundColor = .clear + return hc + }() + + private lazy var progressView: FAPlayerProgressView = { + let view = FAPlayerProgressView() + view.insets = .init(top: 10, left: 16, bottom: 10, right: 16) + view.panFinish = { [weak self] progress in + guard let self = self else { return } + self.viewModel?.seekTo(Float(progress)) + } + return view + }() + + private lazy var textLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 12, weight: .regular); + label.textColor = UIColor(named: .color_FFFFFF)!.withAlphaComponent(0.8) + label.numberOfLines = 2 + return label + }() + + private lazy var shortNameLabel: UILabel = { + let label = UILabel() + label.font = .font(ofSize: 16, weight: .medium) + label.textColor = UIColor(named: .color_FFFFFF) + return label + }() + + private lazy var playButton: UIButton = { + let config = UIButton.Configuration.plain() + + let button = UIButton(configuration: config, primaryAction: UIAction(handler: { [weak self] _ in + self?.fa_viewModel?.userSwitchPlayAndPause() + })) + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + + if self.viewModel?.isPlaying == true || !isCurrent { + button.configuration?.image = UIImage(named: "pause_icon") + } else { + button.configuration?.image = UIImage(named: "play_icon_01") + } + } + return button + }() + + private lazy var collectButton: UIButton = { + var config = UIButton.Configuration.plain() + config.background.backgroundColor = .clear + let button = UIButton(configuration: config) + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + if button.isSelected { + button.configuration?.image = UIImage(named: "collect_star_icon_selected") + } else { + button.configuration?.image = UIImage(named: "collect_star_icon") + } + } + button.addAction(UIAction(handler: { [weak self] _ in + guard let self = self else { return } + guard let shortPlayId = self.shortModel?.short_play_id else { return } + let videoId = (self.model as? FAVideoInfoModel)?.short_play_video_id + let isCollect = !(self.shortModel?.is_collect ?? false) + + FAAPI.requestShortCollect(isCollect: isCollect, shortPlayId: shortPlayId, videoId: videoId, success: nil) + + }), for: .touchUpInside) + + return button + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override init(frame: CGRect) { + super.init(frame: frame) + NotificationCenter.default.addObserver(self, selector: #selector(updateShortCollectStateNotification), name: FAAPI.updateShortCollectStateNotification, object: nil) + + fa_setupLayout() + } + + @MainActor required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "isPlaying" { + playButton.setNeedsUpdateConfiguration() + } + } + + private func updateEp() { +// let model = self.model as? FAVideoInfoModel + let model = self.model as? FAShortPlayModel + let videoInfo = model?.video_info + + + let text = "Ep.##".localizedReplace(text: videoInfo?.episode ?? "") + "/" + "Ep.##".localizedReplace(text: "\(model?.episode_total ?? 0)") + var view = FAPlayerEpUIButton(text: text) + view.clickHandle = { [weak self] in + self?.fa_viewModel?.pushPlayerDetail(self?.shortModel) + } + epButton.rootView = view + } + + private func updateProgress() { + guard durationTime > 0 else { + progressView.progress = 0 + return + } + progressView.progress = currentTime / durationTime + } + + @objc private func updateShortCollectStateNotification(sender: Notification) { + guard let userInfo = sender.userInfo else { return } + guard let shortPlayId = userInfo["id"] as? String else { return } + guard let state = userInfo["state"] as? Bool else { return } + guard shortPlayId == self.shortModel?.short_play_id else { return } + self.shortModel?.is_collect = state + + collectButton.isSelected = state + } + +} + +extension FARecommendPlayerControlView { + + private func fa_setupLayout() { + + addSubview(epButton.view) + addSubview(progressView) + addSubview(textLabel) + addSubview(shortNameLabel) + addSubview(playButton) + addSubview(collectButton) + + + epButton.view.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().offset(-10) + } + + progressView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.centerX.equalToSuperview() + make.bottom.equalTo(epButton.view.snp.top).offset(-8) + } + + textLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.right.lessThanOrEqualToSuperview().offset(-84) + make.bottom.equalTo(progressView.snp.top).offset(1) + } + + shortNameLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(16) + make.bottom.equalTo(textLabel.snp.top).offset(-5) + make.right.lessThanOrEqualToSuperview().offset(-84) + } + + playButton.snp.makeConstraints { make in + make.center.equalToSuperview() + } + + collectButton.snp.makeConstraints { make in + make.top.equalToSuperview().offset(UIScreen.safeTop) + make.right.equalToSuperview().offset(-16) + make.height.equalTo(44) + } + + } +} diff --git a/Fableon/Class/Recommend/VM/FARecommendViewModel.swift b/Fableon/Class/Recommend/VM/FARecommendViewModel.swift new file mode 100644 index 0000000..33b0dda --- /dev/null +++ b/Fableon/Class/Recommend/VM/FARecommendViewModel.swift @@ -0,0 +1,84 @@ +// +// FARecommendViewModel.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/30. +// + +import UIKit +import JXPlayer + +class FARecommendViewModel: JXPlayerListViewModel { + + private(set) var dataArr: [FAShortPlayModel] = [] + + + + var previousEpisode: FAVideoInfoModel? { + guard dataArr.count > 0 else { return nil } + let row = self.currentIndexPath.row - 1 + if row < 0 { return nil } + return dataArr[row].video_info + } + + var nextEpisode: FAVideoInfoModel? { + guard dataArr.count > 0 else { return nil } + let row = self.currentIndexPath.row + 1 + if row >= dataArr.count { return nil } + return dataArr[row].video_info + } + + private func addDataArr(dataArr: [FAShortPlayModel]) { + guard dataArr.count > 0 else { return } + + var indexPaths: [IndexPath] = [] + var startRow = self.dataArr.count + + dataArr.forEach { _ in + indexPaths.append(IndexPath(row: startRow, section: 0)) + startRow += 1 + } + self.dataArr += dataArr + + CATransaction.setCompletionBlock(nil) + CATransaction.begin() + self.playerListVC?.collectionView.insertItems(at: indexPaths) + CATransaction.commit() + } + + func pushPlayerDetail(_ model: FAShortPlayModel?) { + guard let model = model else { return } + + let vc = FAPlayerDetailViewController() + vc.shortPlayId = model.short_play_id + FATool.topViewController?.navigationController?.pushViewController(vc, animated: true) + } +} + + +extension FARecommendViewModel { + + + + func requestDataArr(page: Int, completer: (() -> Void)? = nil) { + + FAAPI.requestRecommendVideo(page: page) { [weak self] listModel in + guard let self = self else { return } + if let listModel = listModel, let list = listModel.list { + if page == 1 { + self.playerListVC?.clearData() + self.dataArr = list + self.playerListVC?.reloadData { [weak self] in + + self?.playerListVC?.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false) + } + } else { + self.addDataArr(dataArr: list) + } +// self.pagination = listModel.pagination + } + completer?() + } + } + +} diff --git a/Fableon/Libs/Empty/FAEmpty.swift b/Fableon/Libs/Empty/FAEmpty.swift new file mode 100644 index 0000000..4362301 --- /dev/null +++ b/Fableon/Libs/Empty/FAEmpty.swift @@ -0,0 +1,27 @@ +// +// FAEmpty.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/14. +// + +import UIKit +import LYEmptyView + + +struct FAEmpty { + + static func fa_emptyView(image: UIImage?, title: String?) -> LYEmptyView { + + let view = LYEmptyView.emptyActionView(with: image, titleStr: title, detailStr: nil, btnTitleStr: nil) { +// btnClickBlock?() + } + + view?.titleLabFont = .font(ofSize: 14, weight: .medium) + view?.titleLabTextColor = .FFFFFF + view?.contentViewOffset = -100 + view?.subViewMargin = 25 + return view! + + } +} diff --git a/Fableon/Libs/FADeviceId/FADeviceIDManager.swift b/Fableon/Libs/FADeviceId/FADeviceIDManager.swift new file mode 100644 index 0000000..3cf00cd --- /dev/null +++ b/Fableon/Libs/FADeviceId/FADeviceIDManager.swift @@ -0,0 +1,26 @@ +// +// FADeviceIDManager.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import Foundation +import UIKit + +class FADeviceIDManager { + static let shared = FADeviceIDManager() + private let key = "com.fableon.uniqueDeviceID" + + private init() {} + + lazy var id: String = { + if let savedID = FAKeychainHelper.shared.read(key: key) { + return savedID + } else { + let newID = UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString + FAKeychainHelper.shared.save(key: key, value: newID) + return newID + } + }() +} diff --git a/Fableon/Libs/FADeviceId/FAKeychainHelper.swift b/Fableon/Libs/FADeviceId/FAKeychainHelper.swift new file mode 100644 index 0000000..31a5586 --- /dev/null +++ b/Fableon/Libs/FADeviceId/FAKeychainHelper.swift @@ -0,0 +1,62 @@ +// +// FAKeychainHelper.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit +import Security + +class FAKeychainHelper { + + static let shared = FAKeychainHelper() + private init() {} + + func save(key: String, value: String) { + if let data = value.data(using: .utf8) { + + // 先删除旧的 + let query = [ + kSecClass: kSecClassGenericPassword, + kSecAttrAccount: key + ] as CFDictionary + SecItemDelete(query) + + // 再保存新的 + let attributes = [ + kSecClass: kSecClassGenericPassword, + kSecAttrAccount: key, + kSecValueData: data, + kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock + ] as CFDictionary + + SecItemAdd(attributes, nil) + } + } + + func read(key: String) -> String? { + let query = [ + kSecClass: kSecClassGenericPassword, + kSecAttrAccount: key, + kSecReturnData: kCFBooleanTrue!, + kSecMatchLimit: kSecMatchLimitOne + ] as CFDictionary + + var dataTypeRef: AnyObject? + let status = SecItemCopyMatching(query, &dataTypeRef) + + if status == errSecSuccess, let data = dataTypeRef as? Data { + return String(data: data, encoding: .utf8) + } + return nil + } + + func delete(key: String) { + let query = [ + kSecClass: kSecClassGenericPassword, + kSecAttrAccount: key + ] as CFDictionary + SecItemDelete(query) + } +} diff --git a/Fableon/Libs/FALocalized/FALocalized.swift b/Fableon/Libs/FALocalized/FALocalized.swift new file mode 100644 index 0000000..ec6729b --- /dev/null +++ b/Fableon/Libs/FALocalized/FALocalized.swift @@ -0,0 +1,110 @@ +// +// FALocalized.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/10/15. +// + + +import UIKit + + +class FALocalized { + + static let manager = FALocalized() + + private let LocalizedUserDefaultsKey = "FALocalized.LocalizedUserDefaultsKey" + private let LocalizedDataUserDefaultsKey = "FALocalized.LocalizedDataUserDefaultsKey" + private let LocalizedDataLocalizedKeyUserDefaultsKey = "FALocalized.LocalizedDataLocalizedKeyUserDefaultsKey" + + + ///多语言数据 + private(set) lazy var localizedData: [String : String]? = UserDefaults.standard.object(forKey: LocalizedDataUserDefaultsKey) as? [String : String] + { + didSet { + UserDefaults.standard.set(localizedData, forKey: LocalizedDataUserDefaultsKey) + UserDefaults.standard.synchronize() + } + } + ///当前语言数据对应的key + private(set) lazy var localizedDataLocalizedKey: String? = UserDefaults.standard.object(forKey: LocalizedDataLocalizedKeyUserDefaultsKey) as? String + { + didSet { + UserDefaults.standard.set(localizedDataLocalizedKey, forKey: LocalizedDataLocalizedKeyUserDefaultsKey) + UserDefaults.standard.synchronize() + } + } + + // 获取当前语言代码(如果用户未手动设置,则返回系统语言) + var currentLocalizedKey: String { + get { + return "en" +// var key = UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) ?? Locale.preferredLanguages.first +// +// if key?.contains("zh-Hans") == true { +// key = "zh" +// } else if key?.contains("zh-Hant") == true { +// key = "zh_hk" +// } else { +// let arr = key?.components(separatedBy: "-") +// key = arr?.first +// } +// return key ?? "en" + } + set { + UserDefaults.standard.set(newValue, forKey: LocalizedUserDefaultsKey) + UserDefaults.standard.synchronize() + } + } + + var mjLocalizedKey: String { + let key = currentLocalizedKey + if key == "zh" { + return "zh-Hans" + } else if key == "zh_hk" { + return "zh-Hant" + } + return key + } + + // 判断是否跟随系统 + var isFollowingSystem: Bool { + return UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) == nil + } + + // 还原为系统默认语言 + func resetToSystemLanguage() { + UserDefaults.standard.removeObject(forKey: LocalizedUserDefaultsKey) + UserDefaults.standard.synchronize() + } + + // 获取本地化字符串 + func localizedString(forKey key: String, tableName: String? = nil) -> String { + if let localizedData = localizedData, + let text = localizedData[key] { + return text + + } else if let selectedLanguage = UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey), + let bundlePath = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"), + let bundle = Bundle(path: bundlePath) { + + return bundle.localizedString(forKey: key, value: nil, table: tableName) + } else { + return NSLocalizedString(key, tableName: tableName, bundle: .main, value: "", comment: "") + } + } + +} + + +extension String { + var localized: String { + var text = FALocalized.manager.localizedString(forKey: self) + text = text.replacingOccurrences(of: "
", with: "\n") + return text + } + + func localizedReplace(text: String?) -> String { + return self.localized.replacingOccurrences(of: "##", with: text ?? "") + } +} diff --git a/Fableon/Libs/FALogin/FALogin.swift b/Fableon/Libs/FALogin/FALogin.swift new file mode 100644 index 0000000..4dcf83d --- /dev/null +++ b/Fableon/Libs/FALogin/FALogin.swift @@ -0,0 +1,60 @@ +// +// FALogin.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit + +let kFAUserTokenDefaultsKey = "kFAUserTokenDefaultsKey" +let kFAUserInfoDefaultsKey = "kFAUserInfoDefaultsKey" + +class FALogin: NSObject { + static let manager = FALogin() + + private(set) var token = UserDefaults.fa_object(forKey: kFAUserTokenDefaultsKey, as: FATokenModel.self) + private(set) var userInfo = UserDefaults.fa_object(forKey: kFAUserInfoDefaultsKey, as: FAUserInfo.self) + + + private func setToken(_ token: FATokenModel?) { + self.token = token + UserDefaults.fa_setObject(token, forKey: kFAUserTokenDefaultsKey) + } + + private func setUserInfo(_ userInfo: FAUserInfo?) { + self.userInfo = userInfo + UserDefaults.fa_setObject(userInfo, forKey: kFAUserInfoDefaultsKey) + } + +} + +extension FALogin { + + func requestUserToken(completer: (() -> Void)?) { + FANetworkManager.manager.request(FABaseURL + "/customer/register") { (response: FANetworkManager.Response) in + if let token = response.data { + self.setToken(token) + } + completer?() + } + } + + func requestUserInfo(completer: (() -> Void)?) { + FANetworkManager.manager.request(FABaseURL + "/customer/info", method: .get) { (response: FANetworkManager.Response) in + if let userInfo = response.data { + self.setUserInfo(userInfo) + NotificationCenter.default.post(name: Self.userInfoUpdateNotification, object: nil) + } + completer?() + } + + } +} + +extension FALogin { + + ///用户信息更新 + @objc static let userInfoUpdateNotification = NSNotification.Name(rawValue: "FALogin.userInfoUpdateNotification") + +} diff --git a/Fableon/Libs/FALogin/FATokenModel.swift b/Fableon/Libs/FALogin/FATokenModel.swift new file mode 100644 index 0000000..d63c1fc --- /dev/null +++ b/Fableon/Libs/FALogin/FATokenModel.swift @@ -0,0 +1,35 @@ +// +// FATokenModel.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit +import SmartCodable + +class FATokenModel: 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 + } +} diff --git a/Fableon/Libs/FALogin/FAUserInfo.swift b/Fableon/Libs/FALogin/FAUserInfo.swift new file mode 100644 index 0000000..74cea37 --- /dev/null +++ b/Fableon/Libs/FALogin/FAUserInfo.swift @@ -0,0 +1,49 @@ +// +// Untitled.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit +import SmartCodable + +class FAUserInfo: NSObject, SmartCodable, NSSecureCoding { + + var id: String? + var customer_id: String? + var is_tourist: Bool? + var family_name: String? + var avator: String? + + func getNickName() -> String { + if let name = family_name, !name.isEmpty { + return name + } else { + return "Visitor" + } + } + + required override init() { } + + static var supportsSecureCoding: Bool { + return true + } + + func encode(with coder: NSCoder) { + coder.encode(id, forKey: "id") + coder.encode(customer_id, forKey: "customer_id") + coder.encode(is_tourist, forKey: "is_tourist") + coder.encode(avator, forKey: "avator") + coder.encode(family_name, forKey: "family_name") + } + + required init?(coder: NSCoder) { + super.init() + id = coder.decodeObject(of: NSString.self, forKey: "id") as? String + customer_id = coder.decodeObject(of: NSString.self, forKey: "customer_id") as? String + is_tourist = coder.decodeObject(of: NSNumber.self, forKey: "is_tourist")?.boolValue + avator = coder.decodeObject(of: NSString.self, forKey: "avator") as? String + family_name = coder.decodeObject(of: NSString.self, forKey: "family_name") as? String + } +} diff --git a/Fableon/Libs/FATool/FATool.swift b/Fableon/Libs/FATool/FATool.swift new file mode 100644 index 0000000..561fb19 --- /dev/null +++ b/Fableon/Libs/FATool/FATool.swift @@ -0,0 +1,54 @@ +// +// FATool.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// +import Foundation +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 FATool { + + static var windowScene: UIWindowScene? + + static var keyWindow: UIWindow? { + return windowScene?.keyWindow + } + + static var rootViewController: UIViewController? { + return keyWindow?.rootViewController + } + + static var topViewController: UIViewController? { + var resultVC: UIViewController? = self.rootViewController + if let rootNav = resultVC as? UINavigationController { + resultVC = rootNav.topViewController + } + + resultVC = self._topViewController(resultVC) + while resultVC?.presentedViewController != nil { + resultVC = self._topViewController(resultVC?.presentedViewController) + } + return resultVC + } + + private static func _topViewController(_ vc: UIViewController?) -> UIViewController? { + if vc is UINavigationController { + return _topViewController((vc as? UINavigationController)?.topViewController) + } else if vc is UITabBarController { + return _topViewController((vc as? UITabBarController)?.selectedViewController) + } else { + return vc + } + } + +} diff --git a/Fableon/Libs/FSPagerView/FAPagerViewTransformer.swift b/Fableon/Libs/FSPagerView/FAPagerViewTransformer.swift new file mode 100644 index 0000000..725657e --- /dev/null +++ b/Fableon/Libs/FSPagerView/FAPagerViewTransformer.swift @@ -0,0 +1,20 @@ +// +// FAPagerViewTransformer.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/16. +// + +import UIKit +import FSPagerView + +class FAPagerViewTransformer: FSPagerViewTransformer { + + + override func proposedInteritemSpacing() -> CGFloat { + guard let pagerView = self.pagerView else { + return 0 + } + return pagerView.interitemSpacing + } +} diff --git a/Fableon/Libs/FSPagerView/FSPagerSwiftUIView.swift b/Fableon/Libs/FSPagerView/FSPagerSwiftUIView.swift new file mode 100644 index 0000000..8b79843 --- /dev/null +++ b/Fableon/Libs/FSPagerView/FSPagerSwiftUIView.swift @@ -0,0 +1,138 @@ +// +// FSPagerSwiftUIView.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + + +import SwiftUI +import FSPagerView + +struct FSPagerSwiftUIView: UIViewRepresentable { + typealias UIViewType = FSPagerView + private let data: [Data] + private let content: (Data) -> Cell + private var transformer: FSPagerViewTransformer? + private var isLoop: Bool = false + private var itemSize: CGSize = .zero + private var didSelect: ((Int) -> Void)? + + + + init( + _ data: [Data], + _ content: @escaping (Data) -> Cell + ) { + self.data = data + self.content = content + } + + func makeCoordinator() -> Coordinator { + return Coordinator(data, self, content) + } + + func makeUIView(context: Context) -> FSPagerView { + let view = FSPagerView() + self.updatePropertyValues(view) + view.dataSource = context.coordinator + view.delegate = context.coordinator + view.register(HostingCell.self, forCellWithReuseIdentifier: "Cell") + return view + } + + func updateUIView(_ uiView: FSPagerView, context: Context) { +// uiView.reloadData() + } + + private func updatePropertyValues(_ pagerView: FSPagerView) { + if pagerView.isInfinite != isLoop { + pagerView.isInfinite = isLoop + } + if pagerView.transformer != transformer { + pagerView.transformer = transformer + } + if pagerView.itemSize != itemSize { + pagerView.itemSize = itemSize + } + } + + // 桥接代理的 Coordinator + class Coordinator: NSObject, FSPagerViewDataSource, FSPagerViewDelegate { + private var data: [Data] + private let content: (Data) -> Cell + private let pagerView: FSPagerSwiftUIView + + init(_ data: [Data], + _ pagerView: FSPagerSwiftUIView, + _ content: @escaping (Data) -> Cell + ) { + self.data = data + self.content = content + self.pagerView = pagerView + } + + func numberOfItems(in pagerView: FSPagerView) -> Int { + return data.count + } + + func pagerView(_ pagerView: FSPagerView, cellForItemAt index: Int) -> FSPagerViewCell { + let cell = pagerView.dequeueReusableCell(withReuseIdentifier: "Cell", at: index) as! HostingCell + let data = self.data[index] + let view = content(data) + cell.setup(with: view) + return cell + } + + func pagerView(_ pagerView: FSPagerView, didSelectItemAt index: Int) { + self.pagerView.didSelect?(index) + } + } +} + + +extension FSPagerSwiftUIView { + func transformer(_ newValue: FSPagerViewTransformer?) -> Self { + var modified = self + modified.transformer = newValue + return modified + } + + func isLoop(_ newValue: Bool) -> Self { + var modified = self + modified.isLoop = newValue + return modified + } + + func itemSize(_ newValue: CGSize) -> Self { + var modified = self + modified.itemSize = newValue + return modified + } +} + +private class HostingCell: FSPagerViewCell { + var host: UIHostingController? + + func setup(with view: Content) { + contentView.layer.shadowOpacity = 0 + + if host == nil { + let controller = UIHostingController(rootView: view) + host = controller + + guard let content = controller.view else { return } + content.backgroundColor = .clear + content.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(content) + + content.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true + content.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true + content.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true + content.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true + } else { + host?.rootView = view + } + setNeedsLayout() + } +} diff --git a/Fableon/Libs/HUD/FAHUD.swift b/Fableon/Libs/HUD/FAHUD.swift new file mode 100644 index 0000000..2e3b026 --- /dev/null +++ b/Fableon/Libs/HUD/FAHUD.swift @@ -0,0 +1,21 @@ +// +// FAHUD.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/29. +// + +import SVProgressHUD + +struct FAHUD { + + static func show(containerView: UIView? = nil, type: SVProgressHUDMaskType = .clear) { + SVProgressHUD.setContainerView(containerView) + SVProgressHUD.setDefaultMaskType(type) + SVProgressHUD.show() + } + + static func dismiss() { + SVProgressHUD.dismiss() + } +} diff --git a/Fableon/Libs/HUD/FAToast.swift b/Fableon/Libs/HUD/FAToast.swift new file mode 100644 index 0000000..c68bb9f --- /dev/null +++ b/Fableon/Libs/HUD/FAToast.swift @@ -0,0 +1,22 @@ +// +// FAToast.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/29. +// + +import Toast + +struct FAToast { + + static func config() { + CSToastManager.setTapToDismissEnabled(false) + CSToastManager.setDefaultDuration(2) + CSToastManager.setDefaultPosition(CSToastPositionCenter) + } + + static func show(text: String?) { + guard let text = text else { return } + FATool.keyWindow?.makeToast(text) + } +} diff --git a/Fableon/Libs/WaterfallFlowLayout/FAWaterfallFlowLayout.swift b/Fableon/Libs/WaterfallFlowLayout/FAWaterfallFlowLayout.swift new file mode 100644 index 0000000..4b31510 --- /dev/null +++ b/Fableon/Libs/WaterfallFlowLayout/FAWaterfallFlowLayout.swift @@ -0,0 +1,191 @@ +// +// FAWaterfallFlowLayout.swift +// Fableon +// +// Created by 长沙鸿瑶 on 2025/9/15. +// + +import UIKit + +@objc protocol FAWaterfallMutiSectionDelegate: NSObjectProtocol { + // 必选delegate实现 + /// collectionItem高度 + func heightForRowAtIndexPath(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, indexPath: IndexPath, itemWidth: CGFloat) -> CGFloat + + // 可选delegate实现 + /// 每个section 列数(默认2列) + @objc optional func columnNumber(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> Int + + /// header高度(默认为0) + @objc optional func referenceSizeForHeader(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGSize + + /// footer高度(默认为0) + @objc optional func referenceSizeForFooter(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGSize + + /// 每个section 边距(默认为0) + @objc optional func insetForSection(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> UIEdgeInsets + + /// 每个section item上下间距(默认为0) + @objc optional func lineSpacing(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGFloat + + /// 每个section item左右间距(默认为0) + @objc optional func interitemSpacing(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGFloat + + /// section头部header与上个section尾部footer间距(默认为0) + @objc optional func spacingWithLastSection(collectionView collection: UICollectionView, layout: FAWaterfallFlowLayout, section: Int) -> CGFloat +} + +class FAWaterfallFlowLayout: UICollectionViewFlowLayout { + weak var delegate: FAWaterfallMutiSectionDelegate? + + private var sectionInsets: UIEdgeInsets = .zero + private var columnCount: Int = 2 + private var lineSpacing: CGFloat = 0 + private var interitemSpacing: CGFloat = 0 + private var headerSize: CGSize = .zero + private var footerSize: CGSize = .zero + + //存放attribute的数组 + private var attrsArray: [UICollectionViewLayoutAttributes] = [] + //存放每个section中各个列的最后一个高度 + private var columnHeights: [CGFloat] = [] + //collectionView的Content的高度 + private var contentHeight: CGFloat = 0 + //记录上个section高度最高一列的高度 + private var lastContentHeight: CGFloat = 0 + //每个section的header与上个section的footer距离 + private var spacingWithLastSection: CGFloat = 0 + + + override func prepare() { + super.prepare() + self.contentHeight = 0 + self.lastContentHeight = 0 + self.spacingWithLastSection = 0 + self.lineSpacing = 0 + self.sectionInsets = .zero + self.headerSize = .zero + self.footerSize = .zero + self.columnHeights.removeAll() + self.attrsArray.removeAll() + + let sectionCount = self.collectionView!.numberOfSections + // 遍历section + for idx in 0.. [UICollectionViewLayoutAttributes]? { + return self.attrsArray + } + + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + if let column = self.delegate?.columnNumber?(collectionView: self.collectionView!, layout: self, section: indexPath.section) { + self.columnCount = column + } + if let lineSpacing = self.delegate?.lineSpacing?(collectionView: self.collectionView!, layout: self, section: indexPath.section) { + self.lineSpacing = lineSpacing + } + if let interitem = self.delegate?.interitemSpacing?(collectionView: self.collectionView!, layout: self, section: indexPath.section) { + self.interitemSpacing = interitem + } + + let attri = UICollectionViewLayoutAttributes(forCellWith: indexPath) + let weight = self.collectionView!.frame.size.width + let itemSpacing = CGFloat(self.columnCount - 1) * self.interitemSpacing + let allWeight = weight - self.sectionInsets.left - self.sectionInsets.right - itemSpacing + let cellWeight = allWeight / CGFloat(self.columnCount) + let cellHeight: CGFloat = (self.delegate?.heightForRowAtIndexPath(collectionView: self.collectionView!, layout: self, indexPath: indexPath, itemWidth: cellWeight))! + + var tmpMinColumn = 0 + var minColumnHeight = self.columnHeights[0] + for i in 0.. columnH { + minColumnHeight = columnH + tmpMinColumn = i + } + } + let cellX = self.sectionInsets.left + CGFloat(tmpMinColumn) * (cellWeight + self.interitemSpacing) + var cellY: CGFloat = 0 + cellY = minColumnHeight + if cellY != self.lastContentHeight { + cellY += self.lineSpacing + } + + if self.contentHeight < minColumnHeight { + self.contentHeight = minColumnHeight + } + + attri.frame = CGRect(x: cellX, y: cellY, width: cellWeight, height: cellHeight) + self.columnHeights[tmpMinColumn] = attri.frame.maxY + //取最大的 + for i in 0.. UICollectionViewLayoutAttributes? { + let attri = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, with: indexPath) + if elementKind == UICollectionView.elementKindSectionHeader { + if let headerSize = self.delegate?.referenceSizeForHeader?(collectionView: self.collectionView!, layout: self, section: indexPath.section) { + self.headerSize = headerSize + } + self.contentHeight += self.spacingWithLastSection + attri.frame = CGRect(x: 0, y: self.contentHeight, width: self.headerSize.width, height: self.headerSize.height) + self.contentHeight += self.headerSize.height + self.contentHeight += self.sectionInsets.top + } else if elementKind == UICollectionView.elementKindSectionFooter { + if let footerSize = self.delegate?.referenceSizeForFooter?(collectionView: self.collectionView!, layout: self, section: indexPath.section) { + self.footerSize = footerSize + } + self.contentHeight += self.sectionInsets.bottom + attri.frame = CGRect(x: 0, y: self.contentHeight, width: self.footerSize.width, height: self.footerSize.height) + self.contentHeight += self.footerSize.height + } + return attri + } + + override var collectionViewContentSize: CGSize { + return CGSize(width: self.collectionView!.frame.size.width, height: self.contentHeight) + } +} diff --git a/Fableon/Source/Assets.xcassets/AccentColor.colorset/Contents.json b/Fableon/Source/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/AppIcon.appiconset/Contents.json b/Fableon/Source/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..011f599 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "Fableon_logo.jpg", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/AppIcon.appiconset/Fableon_logo.jpg b/Fableon/Source/Assets.xcassets/AppIcon.appiconset/Fableon_logo.jpg new file mode 100644 index 0000000..ef4e739 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/AppIcon.appiconset/Fableon_logo.jpg differ diff --git a/Fableon/Source/Assets.xcassets/Contents.json b/Fableon/Source/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/Contents.json b/Fableon/Source/Assets.xcassets/LaunchScreen/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/LaunchScreen/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_bg_image.imageset/4fa48a3ef0637ef7052ed9e3d8e780fc6037d6655e0b7054f37a2f0d4a578175 1@2x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_bg_image.imageset/4fa48a3ef0637ef7052ed9e3d8e780fc6037d6655e0b7054f37a2f0d4a578175 1@2x.png new file mode 100644 index 0000000..13d9aab Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_bg_image.imageset/4fa48a3ef0637ef7052ed9e3d8e780fc6037d6655e0b7054f37a2f0d4a578175 1@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_bg_image.imageset/4fa48a3ef0637ef7052ed9e3d8e780fc6037d6655e0b7054f37a2f0d4a578175 1@3x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_bg_image.imageset/4fa48a3ef0637ef7052ed9e3d8e780fc6037d6655e0b7054f37a2f0d4a578175 1@3x.png new file mode 100644 index 0000000..a1b7c24 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_bg_image.imageset/4fa48a3ef0637ef7052ed9e3d8e780fc6037d6655e0b7054f37a2f0d4a578175 1@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_bg_image.imageset/Contents.json b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_bg_image.imageset/Contents.json new file mode 100644 index 0000000..613d83c --- /dev/null +++ b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_bg_image.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "4fa48a3ef0637ef7052ed9e3d8e780fc6037d6655e0b7054f37a2f0d4a578175 1@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "4fa48a3ef0637ef7052ed9e3d8e780fc6037d6655e0b7054f37a2f0d4a578175 1@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_01.imageset/Contents.json b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_01.imageset/Contents.json new file mode 100644 index 0000000..a65fdcd --- /dev/null +++ b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Fableon_圆角logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Fableon_圆角logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_01.imageset/Fableon_圆角logo@2x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_01.imageset/Fableon_圆角logo@2x.png new file mode 100644 index 0000000..7e72ec8 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_01.imageset/Fableon_圆角logo@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_01.imageset/Fableon_圆角logo@3x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_01.imageset/Fableon_圆角logo@3x.png new file mode 100644 index 0000000..9fd9a17 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_01.imageset/Fableon_圆角logo@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_02.imageset/Contents.json b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_02.imageset/Contents.json new file mode 100644 index 0000000..a65fdcd --- /dev/null +++ b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Fableon_圆角logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Fableon_圆角logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_02.imageset/Fableon_圆角logo@2x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_02.imageset/Fableon_圆角logo@2x.png new file mode 100644 index 0000000..fea16b6 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_02.imageset/Fableon_圆角logo@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_02.imageset/Fableon_圆角logo@3x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_02.imageset/Fableon_圆角logo@3x.png new file mode 100644 index 0000000..a2e1a95 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_logo_icon_02.imageset/Fableon_圆角logo@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_01.imageset/Contents.json b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_01.imageset/Contents.json new file mode 100644 index 0000000..c5d3c27 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Diverse themes@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Diverse themes@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_01.imageset/Diverse themes@2x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_01.imageset/Diverse themes@2x.png new file mode 100644 index 0000000..5cc2e3e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_01.imageset/Diverse themes@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_01.imageset/Diverse themes@3x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_01.imageset/Diverse themes@3x.png new file mode 100644 index 0000000..51c9dfc Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_01.imageset/Diverse themes@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_02.imageset/Contents.json b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_02.imageset/Contents.json new file mode 100644 index 0000000..ac9095c --- /dev/null +++ b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "High-definition playback@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "High-definition playback@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_02.imageset/High-definition playback@2x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_02.imageset/High-definition playback@2x.png new file mode 100644 index 0000000..e0857ed Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_02.imageset/High-definition playback@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_02.imageset/High-definition playback@3x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_02.imageset/High-definition playback@3x.png new file mode 100644 index 0000000..e34e5b1 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_02.imageset/High-definition playback@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_03.imageset/Contents.json b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_03.imageset/Contents.json new file mode 100644 index 0000000..1a2b1f4 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_03.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group 2378@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group 2378@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_03.imageset/Group 2378@2x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_03.imageset/Group 2378@2x.png new file mode 100644 index 0000000..dba89af Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_03.imageset/Group 2378@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_03.imageset/Group 2378@3x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_03.imageset/Group 2378@3x.png new file mode 100644 index 0000000..c17a2de Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_03.imageset/Group 2378@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_04.imageset/Contents.json b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_04.imageset/Contents.json new file mode 100644 index 0000000..1a2b1f4 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_04.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Group 2378@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Group 2378@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_04.imageset/Group 2378@2x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_04.imageset/Group 2378@2x.png new file mode 100644 index 0000000..d375dcb Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_04.imageset/Group 2378@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_04.imageset/Group 2378@3x.png b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_04.imageset/Group 2378@3x.png new file mode 100644 index 0000000..0db8d85 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/LaunchScreen/launch_text_image_04.imageset/Group 2378@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/color/#000000.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#000000.colorset/Contents.json new file mode 100644 index 0000000..7e8f38f --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#000000.colorset/Contents.json @@ -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 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#000000_0.75.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#000000_0.75.colorset/Contents.json new file mode 100644 index 0000000..f1cc9da --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#000000_0.75.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.750", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#0D0D0D.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#0D0D0D.colorset/Contents.json new file mode 100644 index 0000000..17018e3 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#0D0D0D.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x0D", + "green" : "0x0D", + "red" : "0x0D" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#20A2FF.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#20A2FF.colorset/Contents.json new file mode 100644 index 0000000..adccc8a --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#20A2FF.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xA2", + "red" : "0x20" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#333333.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#333333.colorset/Contents.json new file mode 100644 index 0000000..be3a0c9 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#333333.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x33", + "green" : "0x33", + "red" : "0x33" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#35A4FE.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#35A4FE.colorset/Contents.json new file mode 100644 index 0000000..bbc405b --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#35A4FE.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFE", + "green" : "0xA4", + "red" : "0x35" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#3769FC.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#3769FC.colorset/Contents.json new file mode 100644 index 0000000..3b22f35 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#3769FC.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFC", + "green" : "0x69", + "red" : "0x37" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#4D4A4A.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#4D4A4A.colorset/Contents.json new file mode 100644 index 0000000..4cf6ac8 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#4D4A4A.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x4A", + "green" : "0x4A", + "red" : "0x4D" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#52A2F1.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#52A2F1.colorset/Contents.json new file mode 100644 index 0000000..aa0eb85 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#52A2F1.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF1", + "green" : "0xA2", + "red" : "0x52" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#5CA8FF_0.2.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#5CA8FF_0.2.colorset/Contents.json new file mode 100644 index 0000000..56695f2 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#5CA8FF_0.2.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.200", + "blue" : "0xFF", + "green" : "0xA8", + "red" : "0x5C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#6D6D6D_0.4.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#6D6D6D_0.4.colorset/Contents.json new file mode 100644 index 0000000..5ab8f27 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#6D6D6D_0.4.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.400", + "blue" : "0x6D", + "green" : "0x6D", + "red" : "0x6D" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#777777.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#777777.colorset/Contents.json new file mode 100644 index 0000000..8a48289 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#777777.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x77", + "green" : "0x77", + "red" : "0x77" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#81CAFF.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#81CAFF.colorset/Contents.json new file mode 100644 index 0000000..7a0453a --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#81CAFF.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xCA", + "red" : "0x81" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#8B8B8B.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#8B8B8B.colorset/Contents.json new file mode 100644 index 0000000..944680f --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#8B8B8B.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x8B", + "green" : "0x8B", + "red" : "0x8B" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#999999.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#999999.colorset/Contents.json new file mode 100644 index 0000000..867360e --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#999999.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x99", + "green" : "0x99", + "red" : "0x99" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#A8DBFF.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#A8DBFF.colorset/Contents.json new file mode 100644 index 0000000..840f0df --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#A8DBFF.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xDB", + "red" : "0xA8" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#BEDFFF.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#BEDFFF.colorset/Contents.json new file mode 100644 index 0000000..05e72e0 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#BEDFFF.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xDF", + "red" : "0xBE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#C7DEF5.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#C7DEF5.colorset/Contents.json new file mode 100644 index 0000000..d834178 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#C7DEF5.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF5", + "green" : "0xDE", + "red" : "0xC7" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#D9D9D9.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#D9D9D9.colorset/Contents.json new file mode 100644 index 0000000..78408a9 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#D9D9D9.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xD9", + "green" : "0xD9", + "red" : "0xD9" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#DDEDFD.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#DDEDFD.colorset/Contents.json new file mode 100644 index 0000000..9cddc33 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#DDEDFD.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFD", + "green" : "0xED", + "red" : "0xDD" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#F8D01D.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#F8D01D.colorset/Contents.json new file mode 100644 index 0000000..fbbba59 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#F8D01D.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x1D", + "green" : "0xD0", + "red" : "0xF8" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#F94F7F.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#F94F7F.colorset/Contents.json new file mode 100644 index 0000000..e8db97d --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#F94F7F.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x7F", + "green" : "0x4F", + "red" : "0xF9" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#FE6C05.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#FE6C05.colorset/Contents.json new file mode 100644 index 0000000..f32ff1f --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#FE6C05.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x05", + "green" : "0x6C", + "red" : "0xFE" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#FFFFFF.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#FFFFFF.colorset/Contents.json new file mode 100644 index 0000000..fafa476 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#FFFFFF.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/#FFFFFF_0.25.colorset/Contents.json b/Fableon/Source/Assets.xcassets/color/#FFFFFF_0.25.colorset/Contents.json new file mode 100644 index 0000000..44540da --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/#FFFFFF_0.25.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.250", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/color/Contents.json b/Fableon/Source/Assets.xcassets/color/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/color/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Contents.json b/Fableon/Source/Assets.xcassets/image/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2914.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Frame 2914.imageset/Contents.json new file mode 100644 index 0000000..aedb26f --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Frame 2914.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 2914@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 2914@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2914.imageset/Frame 2914@2x.png b/Fableon/Source/Assets.xcassets/image/Frame 2914.imageset/Frame 2914@2x.png new file mode 100644 index 0000000..b298a8d Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 2914.imageset/Frame 2914@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2914.imageset/Frame 2914@3x.png b/Fableon/Source/Assets.xcassets/image/Frame 2914.imageset/Frame 2914@3x.png new file mode 100644 index 0000000..536ad00 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 2914.imageset/Frame 2914@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2915.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Frame 2915.imageset/Contents.json new file mode 100644 index 0000000..aedb26f --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Frame 2915.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 2914@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 2914@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2915.imageset/Frame 2914@2x.png b/Fableon/Source/Assets.xcassets/image/Frame 2915.imageset/Frame 2914@2x.png new file mode 100644 index 0000000..411551b Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 2915.imageset/Frame 2914@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2915.imageset/Frame 2914@3x.png b/Fableon/Source/Assets.xcassets/image/Frame 2915.imageset/Frame 2914@3x.png new file mode 100644 index 0000000..712eaae Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 2915.imageset/Frame 2914@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2916.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Frame 2916.imageset/Contents.json new file mode 100644 index 0000000..14c0c1e --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Frame 2916.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 2916@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 2916@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2916.imageset/Frame 2916@2x.png b/Fableon/Source/Assets.xcassets/image/Frame 2916.imageset/Frame 2916@2x.png new file mode 100644 index 0000000..e386048 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 2916.imageset/Frame 2916@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2916.imageset/Frame 2916@3x.png b/Fableon/Source/Assets.xcassets/image/Frame 2916.imageset/Frame 2916@3x.png new file mode 100644 index 0000000..34d7d79 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 2916.imageset/Frame 2916@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2920.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Frame 2920.imageset/Contents.json new file mode 100644 index 0000000..464036a --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Frame 2920.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 2920@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 2920@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2920.imageset/Frame 2920@2x.png b/Fableon/Source/Assets.xcassets/image/Frame 2920.imageset/Frame 2920@2x.png new file mode 100644 index 0000000..61eff72 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 2920.imageset/Frame 2920@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2920.imageset/Frame 2920@3x.png b/Fableon/Source/Assets.xcassets/image/Frame 2920.imageset/Frame 2920@3x.png new file mode 100644 index 0000000..9de4414 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 2920.imageset/Frame 2920@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2921.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Frame 2921.imageset/Contents.json new file mode 100644 index 0000000..c4c83eb --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Frame 2921.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 2921@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 2921@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2921.imageset/Frame 2921@2x.png b/Fableon/Source/Assets.xcassets/image/Frame 2921.imageset/Frame 2921@2x.png new file mode 100644 index 0000000..ca7352e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 2921.imageset/Frame 2921@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 2921.imageset/Frame 2921@3x.png b/Fableon/Source/Assets.xcassets/image/Frame 2921.imageset/Frame 2921@3x.png new file mode 100644 index 0000000..764c5c6 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 2921.imageset/Frame 2921@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3008.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Frame 3008.imageset/Contents.json new file mode 100644 index 0000000..9b2ae06 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Frame 3008.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 3008@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 3008@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3008.imageset/Frame 3008@2x.png b/Fableon/Source/Assets.xcassets/image/Frame 3008.imageset/Frame 3008@2x.png new file mode 100644 index 0000000..317ca2d Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 3008.imageset/Frame 3008@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3008.imageset/Frame 3008@3x.png b/Fableon/Source/Assets.xcassets/image/Frame 3008.imageset/Frame 3008@3x.png new file mode 100644 index 0000000..bc175e1 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 3008.imageset/Frame 3008@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3009.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Frame 3009.imageset/Contents.json new file mode 100644 index 0000000..14b23d4 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Frame 3009.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 3009@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 3009@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3009.imageset/Frame 3009@2x.png b/Fableon/Source/Assets.xcassets/image/Frame 3009.imageset/Frame 3009@2x.png new file mode 100644 index 0000000..c0ae20f Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 3009.imageset/Frame 3009@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3009.imageset/Frame 3009@3x.png b/Fableon/Source/Assets.xcassets/image/Frame 3009.imageset/Frame 3009@3x.png new file mode 100644 index 0000000..3593837 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 3009.imageset/Frame 3009@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3011.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Frame 3011.imageset/Contents.json new file mode 100644 index 0000000..489ebbd --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Frame 3011.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 3011@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 3011@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3011.imageset/Frame 3011@2x.png b/Fableon/Source/Assets.xcassets/image/Frame 3011.imageset/Frame 3011@2x.png new file mode 100644 index 0000000..75d2439 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 3011.imageset/Frame 3011@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3011.imageset/Frame 3011@3x.png b/Fableon/Source/Assets.xcassets/image/Frame 3011.imageset/Frame 3011@3x.png new file mode 100644 index 0000000..581179c Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 3011.imageset/Frame 3011@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3012.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Frame 3012.imageset/Contents.json new file mode 100644 index 0000000..21c2e6f --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Frame 3012.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 3011@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 3011@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3012.imageset/Frame 3011@2x.png b/Fableon/Source/Assets.xcassets/image/Frame 3012.imageset/Frame 3011@2x.png new file mode 100644 index 0000000..cc097dc Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 3012.imageset/Frame 3011@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3012.imageset/Frame 3011@3x.png b/Fableon/Source/Assets.xcassets/image/Frame 3012.imageset/Frame 3011@3x.png new file mode 100644 index 0000000..86eceaf Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 3012.imageset/Frame 3011@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3015.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Frame 3015.imageset/Contents.json new file mode 100644 index 0000000..52297a0 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Frame 3015.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 3015@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 3015@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3015.imageset/Frame 3015@2x.png b/Fableon/Source/Assets.xcassets/image/Frame 3015.imageset/Frame 3015@2x.png new file mode 100644 index 0000000..f17d4b0 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 3015.imageset/Frame 3015@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Frame 3015.imageset/Frame 3015@3x.png b/Fableon/Source/Assets.xcassets/image/Frame 3015.imageset/Frame 3015@3x.png new file mode 100644 index 0000000..402d213 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Frame 3015.imageset/Frame 3015@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Search.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Search.imageset/Contents.json new file mode 100644 index 0000000..9de231d --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Search.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Search@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Search@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Search.imageset/Search@2x.png b/Fableon/Source/Assets.xcassets/image/Search.imageset/Search@2x.png new file mode 100644 index 0000000..ae3995e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Search.imageset/Search@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Search.imageset/Search@3x.png b/Fableon/Source/Assets.xcassets/image/Search.imageset/Search@3x.png new file mode 100644 index 0000000..a76c05a Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Search.imageset/Search@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Thalire.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/Thalire.imageset/Contents.json new file mode 100644 index 0000000..f48a40a --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/Thalire.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Thalire@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Thalire@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/Thalire.imageset/Thalire@2x.png b/Fableon/Source/Assets.xcassets/image/Thalire.imageset/Thalire@2x.png new file mode 100644 index 0000000..7574e1e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Thalire.imageset/Thalire@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/Thalire.imageset/Thalire@3x.png b/Fableon/Source/Assets.xcassets/image/Thalire.imageset/Thalire@3x.png new file mode 100644 index 0000000..6b3c25c Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/Thalire.imageset/Thalire@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/__question.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/__question.imageset/Contents.json new file mode 100644 index 0000000..e717cc1 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/__question.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "__question@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "__question@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/__question.imageset/__question@2x.png b/Fableon/Source/Assets.xcassets/image/__question.imageset/__question@2x.png new file mode 100644 index 0000000..224eabf Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/__question.imageset/__question@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/__question.imageset/__question@3x.png b/Fableon/Source/Assets.xcassets/image/__question.imageset/__question@3x.png new file mode 100644 index 0000000..e39f084 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/__question.imageset/__question@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/__shop-72.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/__shop-72.imageset/Contents.json new file mode 100644 index 0000000..684fc30 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/__shop-72.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "__shop-72@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "__shop-72@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/__shop-72.imageset/__shop-72@2x.png b/Fableon/Source/Assets.xcassets/image/__shop-72.imageset/__shop-72@2x.png new file mode 100644 index 0000000..bae0ac2 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/__shop-72.imageset/__shop-72@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/__shop-72.imageset/__shop-72@3x.png b/Fableon/Source/Assets.xcassets/image/__shop-72.imageset/__shop-72@3x.png new file mode 100644 index 0000000..4477027 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/__shop-72.imageset/__shop-72@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/collect_star_icon.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/collect_star_icon.imageset/Contents.json new file mode 100644 index 0000000..1b812ae --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/collect_star_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Star 19@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Star 19@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/collect_star_icon.imageset/Star 19@2x.png b/Fableon/Source/Assets.xcassets/image/collect_star_icon.imageset/Star 19@2x.png new file mode 100644 index 0000000..d1178a2 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/collect_star_icon.imageset/Star 19@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/collect_star_icon.imageset/Star 19@3x.png b/Fableon/Source/Assets.xcassets/image/collect_star_icon.imageset/Star 19@3x.png new file mode 100644 index 0000000..46995fb Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/collect_star_icon.imageset/Star 19@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/collect_star_icon_selected.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/collect_star_icon_selected.imageset/Contents.json new file mode 100644 index 0000000..356b590 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/collect_star_icon_selected.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Star 20@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Star 20@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/collect_star_icon_selected.imageset/Star 20@2x.png b/Fableon/Source/Assets.xcassets/image/collect_star_icon_selected.imageset/Star 20@2x.png new file mode 100644 index 0000000..e269009 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/collect_star_icon_selected.imageset/Star 20@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/collect_star_icon_selected.imageset/Star 20@3x.png b/Fableon/Source/Assets.xcassets/image/collect_star_icon_selected.imageset/Star 20@3x.png new file mode 100644 index 0000000..66778e3 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/collect_star_icon_selected.imageset/Star 20@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/done.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/done.imageset/Contents.json new file mode 100644 index 0000000..f4ac957 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/done.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "done@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "done@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/image/done.imageset/done@2x.png b/Fableon/Source/Assets.xcassets/image/done.imageset/done@2x.png new file mode 100644 index 0000000..0a5c70e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/done.imageset/done@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/done.imageset/done@3x.png b/Fableon/Source/Assets.xcassets/image/done.imageset/done@3x.png new file mode 100644 index 0000000..51607b8 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/done.imageset/done@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_about.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/icon_about.imageset/Contents.json new file mode 100644 index 0000000..b131e99 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/icon_about.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_about@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_about@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/icon_about.imageset/icon_about@2x.png b/Fableon/Source/Assets.xcassets/image/icon_about.imageset/icon_about@2x.png new file mode 100644 index 0000000..5e20904 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_about.imageset/icon_about@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_about.imageset/icon_about@3x.png b/Fableon/Source/Assets.xcassets/image/icon_about.imageset/icon_about@3x.png new file mode 100644 index 0000000..9453e2c Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_about.imageset/icon_about@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_feedback.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/icon_feedback.imageset/Contents.json new file mode 100644 index 0000000..bdd80ac --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/icon_feedback.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_feedback@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_feedback@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/icon_feedback.imageset/icon_feedback@2x.png b/Fableon/Source/Assets.xcassets/image/icon_feedback.imageset/icon_feedback@2x.png new file mode 100644 index 0000000..e5dd1c4 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_feedback.imageset/icon_feedback@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_feedback.imageset/icon_feedback@3x.png b/Fableon/Source/Assets.xcassets/image/icon_feedback.imageset/icon_feedback@3x.png new file mode 100644 index 0000000..88d1740 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_feedback.imageset/icon_feedback@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_privacy.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/icon_privacy.imageset/Contents.json new file mode 100644 index 0000000..2926308 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/icon_privacy.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_privacy@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_privacy@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/icon_privacy.imageset/icon_privacy@2x.png b/Fableon/Source/Assets.xcassets/image/icon_privacy.imageset/icon_privacy@2x.png new file mode 100644 index 0000000..c5f0c9f Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_privacy.imageset/icon_privacy@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_privacy.imageset/icon_privacy@3x.png b/Fableon/Source/Assets.xcassets/image/icon_privacy.imageset/icon_privacy@3x.png new file mode 100644 index 0000000..923f43c Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_privacy.imageset/icon_privacy@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_setting.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/icon_setting.imageset/Contents.json new file mode 100644 index 0000000..0e44388 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/icon_setting.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_setting@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_setting@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/icon_setting.imageset/icon_setting@2x.png b/Fableon/Source/Assets.xcassets/image/icon_setting.imageset/icon_setting@2x.png new file mode 100644 index 0000000..673e755 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_setting.imageset/icon_setting@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_setting.imageset/icon_setting@3x.png b/Fableon/Source/Assets.xcassets/image/icon_setting.imageset/icon_setting@3x.png new file mode 100644 index 0000000..8b7db4e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_setting.imageset/icon_setting@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_user.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/icon_user.imageset/Contents.json new file mode 100644 index 0000000..482e766 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/icon_user.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_user@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_user@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/icon_user.imageset/icon_user@2x.png b/Fableon/Source/Assets.xcassets/image/icon_user.imageset/icon_user@2x.png new file mode 100644 index 0000000..b89bbf2 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_user.imageset/icon_user@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_user.imageset/icon_user@3x.png b/Fableon/Source/Assets.xcassets/image/icon_user.imageset/icon_user@3x.png new file mode 100644 index 0000000..df7c058 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_user.imageset/icon_user@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_visit.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/icon_visit.imageset/Contents.json new file mode 100644 index 0000000..49f92fb --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/icon_visit.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "icon_visit@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "icon_visit@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/icon_visit.imageset/icon_visit@2x.png b/Fableon/Source/Assets.xcassets/image/icon_visit.imageset/icon_visit@2x.png new file mode 100644 index 0000000..e10ea0e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_visit.imageset/icon_visit@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/icon_visit.imageset/icon_visit@3x.png b/Fableon/Source/Assets.xcassets/image/icon_visit.imageset/icon_visit@3x.png new file mode 100644 index 0000000..42161d3 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/icon_visit.imageset/icon_visit@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/logo_image_01.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/logo_image_01.imageset/Contents.json new file mode 100644 index 0000000..cde9f14 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/logo_image_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Fableon_圆角logo 2@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Fableon_圆角logo 2@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/logo_image_01.imageset/Fableon_圆角logo 2@2x.png b/Fableon/Source/Assets.xcassets/image/logo_image_01.imageset/Fableon_圆角logo 2@2x.png new file mode 100644 index 0000000..5e09d83 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/logo_image_01.imageset/Fableon_圆角logo 2@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/logo_image_01.imageset/Fableon_圆角logo 2@3x.png b/Fableon/Source/Assets.xcassets/image/logo_image_01.imageset/Fableon_圆角logo 2@3x.png new file mode 100644 index 0000000..b0199f7 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/logo_image_01.imageset/Fableon_圆角logo 2@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/pause_icon.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/pause_icon.imageset/Contents.json new file mode 100644 index 0000000..20f7a8c --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/pause_icon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "pause@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "pause@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/pause_icon.imageset/pause@2x.png b/Fableon/Source/Assets.xcassets/image/pause_icon.imageset/pause@2x.png new file mode 100644 index 0000000..3b67c13 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/pause_icon.imageset/pause@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/pause_icon.imageset/pause@3x.png b/Fableon/Source/Assets.xcassets/image/pause_icon.imageset/pause@3x.png new file mode 100644 index 0000000..219691e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/pause_icon.imageset/pause@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/placeholder_image.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/placeholder_image.imageset/Contents.json new file mode 100644 index 0000000..dabdab3 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/placeholder_image.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "首页new模块占位图@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "首页new模块占位图@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/placeholder_image.imageset/首页new模块占位图@2x.png b/Fableon/Source/Assets.xcassets/image/placeholder_image.imageset/首页new模块占位图@2x.png new file mode 100644 index 0000000..6677055 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/placeholder_image.imageset/首页new模块占位图@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/placeholder_image.imageset/首页new模块占位图@3x.png b/Fableon/Source/Assets.xcassets/image/placeholder_image.imageset/首页new模块占位图@3x.png new file mode 100644 index 0000000..da85665 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/placeholder_image.imageset/首页new模块占位图@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/play_icon_01.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/play_icon_01.imageset/Contents.json new file mode 100644 index 0000000..c9f3912 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/play_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "play@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "play@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/play_icon_01.imageset/play@2x.png b/Fableon/Source/Assets.xcassets/image/play_icon_01.imageset/play@2x.png new file mode 100644 index 0000000..db39708 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/play_icon_01.imageset/play@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/play_icon_01.imageset/play@3x.png b/Fableon/Source/Assets.xcassets/image/play_icon_01.imageset/play@3x.png new file mode 100644 index 0000000..2674ddf Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/play_icon_01.imageset/play@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/删除.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/删除.imageset/Contents.json new file mode 100644 index 0000000..ce54822 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/删除.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "删除@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "删除@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/删除.imageset/删除@2x.png b/Fableon/Source/Assets.xcassets/image/删除.imageset/删除@2x.png new file mode 100644 index 0000000..a1ee108 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/删除.imageset/删除@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/删除.imageset/删除@3x.png b/Fableon/Source/Assets.xcassets/image/删除.imageset/删除@3x.png new file mode 100644 index 0000000..dedb663 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/删除.imageset/删除@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/历史记录_icon.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/历史记录_icon.imageset/Contents.json new file mode 100644 index 0000000..ff85ffb --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/历史记录_icon.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "历史记录_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "历史记录_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/image/历史记录_icon.imageset/历史记录_icon@2x.png b/Fableon/Source/Assets.xcassets/image/历史记录_icon.imageset/历史记录_icon@2x.png new file mode 100644 index 0000000..300150a Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/历史记录_icon.imageset/历史记录_icon@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/历史记录_icon.imageset/历史记录_icon@3x.png b/Fableon/Source/Assets.xcassets/image/历史记录_icon.imageset/历史记录_icon@3x.png new file mode 100644 index 0000000..939e805 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/历史记录_icon.imageset/历史记录_icon@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/编辑_icon.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/编辑_icon.imageset/Contents.json new file mode 100644 index 0000000..8cf979e --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/编辑_icon.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "编辑_icon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "编辑_icon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/image/编辑_icon.imageset/编辑_icon@2x.png b/Fableon/Source/Assets.xcassets/image/编辑_icon.imageset/编辑_icon@2x.png new file mode 100644 index 0000000..835acd1 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/编辑_icon.imageset/编辑_icon@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/编辑_icon.imageset/编辑_icon@3x.png b/Fableon/Source/Assets.xcassets/image/编辑_icon.imageset/编辑_icon@3x.png new file mode 100644 index 0000000..fb1c6a2 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/编辑_icon.imageset/编辑_icon@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/背景.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/背景.imageset/Contents.json new file mode 100644 index 0000000..1057d71 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/背景.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "背景@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "背景@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/背景.imageset/背景@2x.png b/Fableon/Source/Assets.xcassets/image/背景.imageset/背景@2x.png new file mode 100644 index 0000000..45fb88c Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/背景.imageset/背景@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/背景.imageset/背景@3x.png b/Fableon/Source/Assets.xcassets/image/背景.imageset/背景@3x.png new file mode 100644 index 0000000..307d06e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/背景.imageset/背景@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/路径 264.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/路径 264.imageset/Contents.json new file mode 100644 index 0000000..ce54822 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/路径 264.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "删除@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "删除@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/路径 264.imageset/删除@2x.png b/Fableon/Source/Assets.xcassets/image/路径 264.imageset/删除@2x.png new file mode 100644 index 0000000..3212d71 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/路径 264.imageset/删除@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/路径 264.imageset/删除@3x.png b/Fableon/Source/Assets.xcassets/image/路径 264.imageset/删除@3x.png new file mode 100644 index 0000000..3ec240d Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/路径 264.imageset/删除@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/首页搜索i_ic.imageset/Contents.json b/Fableon/Source/Assets.xcassets/image/首页搜索i_ic.imageset/Contents.json new file mode 100644 index 0000000..e5b47b1 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/image/首页搜索i_ic.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "首页搜索i_ic@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "首页搜索i_ic@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/image/首页搜索i_ic.imageset/首页搜索i_ic@2x.png b/Fableon/Source/Assets.xcassets/image/首页搜索i_ic.imageset/首页搜索i_ic@2x.png new file mode 100644 index 0000000..90f7539 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/首页搜索i_ic.imageset/首页搜索i_ic@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/image/首页搜索i_ic.imageset/首页搜索i_ic@3x.png b/Fableon/Source/Assets.xcassets/image/首页搜索i_ic.imageset/首页搜索i_ic@3x.png new file mode 100644 index 0000000..ce0a3d4 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/image/首页搜索i_ic.imageset/首页搜索i_ic@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/Contents.json b/Fableon/Source/Assets.xcassets/tabbar/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/tabbar/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon.imageset/Contents.json b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon.imageset/Contents.json new file mode 100644 index 0000000..ab8daaf --- /dev/null +++ b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "收藏_collect_off@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "收藏_collect_off@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon.imageset/收藏_collect_off@2x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon.imageset/收藏_collect_off@2x.png new file mode 100644 index 0000000..555330e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon.imageset/收藏_collect_off@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon.imageset/收藏_collect_off@3x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon.imageset/收藏_collect_off@3x.png new file mode 100644 index 0000000..c1092b5 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon.imageset/收藏_collect_off@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon_selected.imageset/Contents.json b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon_selected.imageset/Contents.json new file mode 100644 index 0000000..7a1da52 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon_selected.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "收藏_collect_on@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "收藏_collect_on@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon_selected.imageset/收藏_collect_on@2x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon_selected.imageset/收藏_collect_on@2x.png new file mode 100644 index 0000000..9796bff Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon_selected.imageset/收藏_collect_on@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon_selected.imageset/收藏_collect_on@3x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon_selected.imageset/收藏_collect_on@3x.png new file mode 100644 index 0000000..283104b Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_collect_icon_selected.imageset/收藏_collect_on@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon.imageset/Contents.json b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon.imageset/Contents.json new file mode 100644 index 0000000..19f9078 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "首页_home_off@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "首页_home_off@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon.imageset/首页_home_off@2x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon.imageset/首页_home_off@2x.png new file mode 100644 index 0000000..15db296 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon.imageset/首页_home_off@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon.imageset/首页_home_off@3x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon.imageset/首页_home_off@3x.png new file mode 100644 index 0000000..7cf4556 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon.imageset/首页_home_off@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon_selected.imageset/Contents.json b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon_selected.imageset/Contents.json new file mode 100644 index 0000000..9336538 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon_selected.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "首页_home_on@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "首页_home_on@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon_selected.imageset/首页_home_on@2x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon_selected.imageset/首页_home_on@2x.png new file mode 100644 index 0000000..8a5394e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon_selected.imageset/首页_home_on@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon_selected.imageset/首页_home_on@3x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon_selected.imageset/首页_home_on@3x.png new file mode 100644 index 0000000..b1b8b55 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_home_icon_selected.imageset/首页_home_on@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon.imageset/Contents.json b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon.imageset/Contents.json new file mode 100644 index 0000000..c8c7c41 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "个人中心_me_off@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "个人中心_me_off@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon.imageset/个人中心_me_off@2x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon.imageset/个人中心_me_off@2x.png new file mode 100644 index 0000000..3eb082e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon.imageset/个人中心_me_off@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon.imageset/个人中心_me_off@3x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon.imageset/个人中心_me_off@3x.png new file mode 100644 index 0000000..a7d0d3e Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon.imageset/个人中心_me_off@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon_selected.imageset/Contents.json b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon_selected.imageset/Contents.json new file mode 100644 index 0000000..7ff7920 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon_selected.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "个人中心_me_on@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "个人中心_me_on@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon_selected.imageset/个人中心_me_on@2x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon_selected.imageset/个人中心_me_on@2x.png new file mode 100644 index 0000000..00da49d Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon_selected.imageset/个人中心_me_on@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon_selected.imageset/个人中心_me_on@3x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon_selected.imageset/个人中心_me_on@3x.png new file mode 100644 index 0000000..2d4adea Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_me_icon_selected.imageset/个人中心_me_on@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon.imageset/Contents.json b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon.imageset/Contents.json new file mode 100644 index 0000000..c314fa4 --- /dev/null +++ b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "推荐_reco_off@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "推荐_reco_off@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon.imageset/推荐_reco_off@2x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon.imageset/推荐_reco_off@2x.png new file mode 100644 index 0000000..d496eb5 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon.imageset/推荐_reco_off@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon.imageset/推荐_reco_off@3x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon.imageset/推荐_reco_off@3x.png new file mode 100644 index 0000000..e7d1088 Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon.imageset/推荐_reco_off@3x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon_selected.imageset/Contents.json b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon_selected.imageset/Contents.json new file mode 100644 index 0000000..79e914a --- /dev/null +++ b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon_selected.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "推荐_reco_on@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "推荐_reco_on@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon_selected.imageset/推荐_reco_on@2x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon_selected.imageset/推荐_reco_on@2x.png new file mode 100644 index 0000000..3ab538a Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon_selected.imageset/推荐_reco_on@2x.png differ diff --git a/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon_selected.imageset/推荐_reco_on@3x.png b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon_selected.imageset/推荐_reco_on@3x.png new file mode 100644 index 0000000..ea17eee Binary files /dev/null and b/Fableon/Source/Assets.xcassets/tabbar/tabbar_recommend_icon_selected.imageset/推荐_reco_on@3x.png differ diff --git a/Fableon/Source/Base.lproj/LaunchScreen.storyboard b/Fableon/Source/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..e457da0 --- /dev/null +++ b/Fableon/Source/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Fableon/Source/Fableon-Bridging-Header.h b/Fableon/Source/Fableon-Bridging-Header.h new file mode 100644 index 0000000..f84fb54 --- /dev/null +++ b/Fableon/Source/Fableon-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + + diff --git a/Fableon/Source/Info.plist b/Fableon/Source/Info.plist new file mode 100644 index 0000000..0eb786d --- /dev/null +++ b/Fableon/Source/Info.plist @@ -0,0 +1,23 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + + diff --git a/Fableon/Source/en.lproj/Localizable.strings b/Fableon/Source/en.lproj/Localizable.strings new file mode 100644 index 0000000..af55d02 --- /dev/null +++ b/Fableon/Source/en.lproj/Localizable.strings @@ -0,0 +1,18 @@ + + +"Ep.##" = "Ep.##"; +"network_error_01" = "The service is abnormal. Check the network."; +"Error" = "Error"; +"Feedback" = "Feedback"; +"Collect" = "Collect"; +"History" = "History"; +"Search" = "Search"; +"Historical search" = "Historical search"; +"empty_title_01" = "No search results"; +"empty_title_02" = "There is no data for the moment."; +"About" = "About"; +"Privacy Policy" = "Privacy Policy"; +"User Agreement" = "User Agreement"; +"Visit Website" = "Visit Website"; +"Setting" = "Setting"; +"Settings" = "Settings"; diff --git a/Podfile b/Podfile new file mode 100644 index 0000000..c517c96 --- /dev/null +++ b/Podfile @@ -0,0 +1,37 @@ +platform :ios, '13.0' + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" + config.build_settings['EXCLUDED_ARCHITECTURES'] = 'i386' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' + end + end +end + +target 'Fableon' do + use_frameworks! + + pod 'YYCategories' + pod 'YYText' + pod 'Alamofire' + pod 'SmartCodable' + pod 'JXPlayer', '~> 0.1.5' + pod 'Kingfisher' + pod 'SnapKit' + pod 'FSPagerView' + pod 'HWPanModal' + pod 'SVProgressHUD' + pod 'Toast' + pod 'MJRefresh' + pod 'IQKeyboardManagerSwift' + pod 'LYEmptyView' + pod 'collection-view-layouts/TagsLayout' + pod 'FDFullscreenPopGesture' + +# swiftUI +# pod 'SwiftUIPager' + +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 0000000..6fa1916 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,215 @@ +PODS: + - Alamofire (5.10.2) + - collection-view-layouts/Core (0.2.2) + - collection-view-layouts/TagsLayout (0.2.2): + - collection-view-layouts/Core + - FDFullscreenPopGesture (1.1) + - FSPagerView (0.8.3) + - HWPanModal (0.9.9) + - IQKeyboardCore (1.0.8) + - IQKeyboardManagerSwift (8.0.1): + - IQKeyboardManagerSwift/Appearance (= 8.0.1) + - IQKeyboardManagerSwift/Core (= 8.0.1) + - IQKeyboardManagerSwift/IQKeyboardReturnManager (= 8.0.1) + - IQKeyboardManagerSwift/IQKeyboardToolbarManager (= 8.0.1) + - IQKeyboardManagerSwift/IQTextView (= 8.0.1) + - IQKeyboardManagerSwift/Resign (= 8.0.1) + - IQKeyboardManagerSwift/Appearance (8.0.1): + - IQKeyboardManagerSwift/Core + - IQKeyboardManagerSwift/Core (8.0.1): + - IQKeyboardNotification + - IQTextInputViewNotification + - IQKeyboardManagerSwift/IQKeyboardReturnManager (8.0.1): + - IQKeyboardReturnManager + - IQKeyboardManagerSwift/IQKeyboardToolbarManager (8.0.1): + - IQKeyboardManagerSwift/Core + - IQKeyboardToolbarManager + - IQKeyboardManagerSwift/IQTextView (8.0.1): + - IQTextView + - IQKeyboardManagerSwift/Resign (8.0.1): + - IQKeyboardManagerSwift/Core + - IQKeyboardNotification (1.0.6) + - IQKeyboardReturnManager (1.0.6): + - IQKeyboardCore + - IQKeyboardToolbar (1.1.2): + - IQKeyboardToolbar/Core (= 1.1.2) + - IQKeyboardToolbar/Core (1.1.2): + - IQKeyboardCore + - IQKeyboardToolbar/Placeholderable + - IQKeyboardToolbar/Placeholderable (1.1.2) + - IQKeyboardToolbarManager (1.1.4): + - IQKeyboardToolbar + - IQTextInputViewNotification + - IQTextInputViewNotification (1.0.9): + - IQKeyboardCore + - IQTextView (1.0.5): + - IQKeyboardToolbar/Placeholderable + - JXPlayer (0.1.5): + - SJMediaCacheServer + - SJVideoPlayer + - Kingfisher (8.5.0) + - LYEmptyView (1.3.1) + - Masonry (1.1.0) + - MJRefresh (3.7.9) + - SJBaseVideoPlayer (3.7.7.1): + - Masonry + - SJBaseVideoPlayer/AVPlayer (= 3.7.7.1) + - SJBaseVideoPlayer/Common (= 3.7.7.1) + - SJUIKit/AttributesFactory (>= 0.0.0.38) + - SJUIKit/ObserverHelper + - SJUIKit/Queues + - SJUIKit/SQLite3 + - SJBaseVideoPlayer/AVPlayer (3.7.7.1): + - Masonry + - SJBaseVideoPlayer/Common + - SJUIKit/AttributesFactory (>= 0.0.0.38) + - SJUIKit/ObserverHelper + - SJUIKit/Queues + - SJUIKit/SQLite3 + - SJBaseVideoPlayer/Common (3.7.7.1): + - Masonry + - SJBaseVideoPlayer/ResourceLoader + - SJUIKit/AttributesFactory (>= 0.0.0.38) + - SJUIKit/ObserverHelper + - SJUIKit/Queues + - SJUIKit/SQLite3 + - SJBaseVideoPlayer/ResourceLoader (3.7.7.1): + - Masonry + - SJUIKit/AttributesFactory (>= 0.0.0.38) + - SJUIKit/ObserverHelper + - SJUIKit/Queues + - SJUIKit/SQLite3 + - SJMediaCacheServer (2.1.7): + - SJMediaCacheServer/Core (= 2.1.7) + - SJUIKit/SQLite3 + - SJMediaCacheServer/Core (2.1.7): + - SJUIKit/SQLite3 + - SJUIKit/AttributesFactory (0.0.0.59): + - SJUIKit/AttributesFactory/Deprecated (= 0.0.0.59) + - SJUIKit/AttributesFactory/UIKitText (= 0.0.0.59) + - SJUIKit/AttributesFactory/Deprecated (0.0.0.59) + - SJUIKit/AttributesFactory/UIKitText (0.0.0.59) + - SJUIKit/ObserverHelper (0.0.0.59) + - SJUIKit/Queues (0.0.0.59) + - SJUIKit/SQLite3 (0.0.0.59): + - SJUIKit/SQLite3/Core (= 0.0.0.59) + - SJUIKit/SQLite3/Protocol (= 0.0.0.59) + - YYModel + - SJUIKit/SQLite3/Core (0.0.0.59): + - SJUIKit/SQLite3/Protocol + - YYModel + - SJUIKit/SQLite3/Protocol (0.0.0.59): + - YYModel + - SJVideoPlayer (3.4.3): + - SJBaseVideoPlayer (>= 3.7.5) + - SJVideoPlayer/Common (= 3.4.3) + - SJVideoPlayer/ControlLayers (= 3.4.3) + - SJVideoPlayer/ResourceLoader (= 3.4.3) + - SJVideoPlayer/Common (3.4.3): + - Masonry + - SJBaseVideoPlayer (>= 3.7.5) + - SJUIKit/AttributesFactory + - SJVideoPlayer/ResourceLoader + - SJVideoPlayer/ControlLayers (3.4.3): + - SJBaseVideoPlayer (>= 3.7.5) + - SJVideoPlayer/Common + - SJVideoPlayer/ResourceLoader (3.4.3): + - SJBaseVideoPlayer (>= 3.7.5) + - SmartCodable (5.1.2): + - SmartCodable/Core (= 5.1.2) + - SmartCodable/Core (5.1.2) + - SnapKit (5.7.1) + - SVProgressHUD (2.3.1): + - SVProgressHUD/Core (= 2.3.1) + - SVProgressHUD/Core (2.3.1) + - Toast (4.1.1) + - YYCategories (1.0.4): + - YYCategories/no-arc (= 1.0.4) + - YYCategories/no-arc (1.0.4) + - YYModel (1.0.4) + - YYText (1.0.7) + +DEPENDENCIES: + - Alamofire + - collection-view-layouts/TagsLayout + - FDFullscreenPopGesture + - FSPagerView + - HWPanModal + - IQKeyboardManagerSwift + - JXPlayer (~> 0.1.5) + - Kingfisher + - LYEmptyView + - MJRefresh + - SmartCodable + - SnapKit + - SVProgressHUD + - Toast + - YYCategories + - YYText + +SPEC REPOS: + trunk: + - Alamofire + - collection-view-layouts + - FDFullscreenPopGesture + - FSPagerView + - HWPanModal + - IQKeyboardCore + - IQKeyboardManagerSwift + - IQKeyboardNotification + - IQKeyboardReturnManager + - IQKeyboardToolbar + - IQKeyboardToolbarManager + - IQTextInputViewNotification + - IQTextView + - JXPlayer + - Kingfisher + - LYEmptyView + - Masonry + - MJRefresh + - SJBaseVideoPlayer + - SJMediaCacheServer + - SJUIKit + - SJVideoPlayer + - SmartCodable + - SnapKit + - SVProgressHUD + - Toast + - YYCategories + - YYModel + - YYText + +SPEC CHECKSUMS: + Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496 + collection-view-layouts: 474ec4cce601a26247737227a97fa6a4eb30213f + FDFullscreenPopGesture: a8a620179e3d9c40e8e00256dcee1c1a27c6d0f0 + FSPagerView: 670405b2f18e2a87fa37f20b00de783e562c25a8 + HWPanModal: b57a6717d3cdcd666bff44f9dd2a5be9f4d6f5d2 + IQKeyboardCore: 8652977ec919cf5351aa2977fedd1a6546476fbc + IQKeyboardManagerSwift: 835fc9c6e4732398113406d84900ad2e8f141218 + IQKeyboardNotification: eb4910401f5a0e68f97e71c62f8a0c5b7e9d535c + IQKeyboardReturnManager: fcbf51fc68d7536fc1fbcca5231c4e82576b12ac + IQKeyboardToolbar: a8aab764a27d55892b951e58ebfffdde14a01ce8 + IQKeyboardToolbarManager: c8a575e8b5fffe5873d0e75312244498a0759473 + IQTextInputViewNotification: 3b9fb27a16e7ee8958cc9092cfb07a1a9e1fd559 + IQTextView: ae13b4922f22e6f027f62c557d9f4f236b19d5c7 + JXPlayer: 0e0cd9a2ee406ebb3051d00c760cd5fc599bf09b + Kingfisher: ff0d31a1f07bdff6a1ebb3ba08b8e6e567b6500c + LYEmptyView: b6d418cfa38b78df0cf243f9a9c25ccbdc399922 + Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 + MJRefresh: ff9e531227924c84ce459338414550a05d2aea78 + SJBaseVideoPlayer: b3122de12225b27b71bd9a8a1f08f4dcf2f4e5ec + SJMediaCacheServer: 0ca17fcdd5340fe1d2ad03110e511376f290d924 + SJUIKit: a40111941e408cc17d4c1c23495aa92999e814b0 + SJVideoPlayer: 4f09814f58522e0975cb2dccfda925f6c8643467 + SmartCodable: 545dd052990fe7e80085463b79a1a5e462ee29ff + SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a + SVProgressHUD: 4837c74bdfe2e51e8821c397825996a8d7de6e22 + Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e + YYCategories: 6bcd4314c6661a561410dce4a793379ebd306abd + YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30 + YYText: 5c461d709e24d55a182d1441c41dc639a18a4849 + +PODFILE CHECKSUM: f13e3a9a8d2998f8380682e57a9ed999020f6a46 + +COCOAPODS: 1.16.2