diff --git a/Podfile b/Podfile index 2b1935a..ac19a15 100644 --- a/Podfile +++ b/Podfile @@ -27,5 +27,6 @@ target 'Veloria' do pod 'HWPanModal' #底部弹出控制器 pod 'Kingfisher' #图片加载 pod "ESTabBarController-swift" + pod 'WMZPageController' #分页控制器 end diff --git a/Podfile.lock b/Podfile.lock index 988c46a..38ae34b 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -16,6 +16,7 @@ PODS: - SmartCodable/Core (5.0.9) - SnapKit (5.7.1) - Toast (4.1.1) + - WMZPageController (1.5.5) - YYKit (1.0.9): - YYKit/no-arc (= 1.0.9) - YYKit/no-arc (1.0.9) @@ -33,6 +34,7 @@ DEPENDENCIES: - SmartCodable - SnapKit - Toast + - WMZPageController - YYKit - ZFPlayer/AVPlayer @@ -49,6 +51,7 @@ SPEC REPOS: - SmartCodable - SnapKit - Toast + - WMZPageController - YYKit - ZFPlayer @@ -64,9 +67,10 @@ SPEC CHECKSUMS: SmartCodable: 68b3598438181a938eed8ee5623e58ef3e3ea443 SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e + WMZPageController: 87dd82d1e3528cd362de19b9a74fd6890d6e1906 YYKit: 7cda43304a8dc3696c449041e2cb3107b4e236e7 ZFPlayer: 5cf39e8d9f0c2394a014b0db4767b5b5a6bffe13 -PODFILE CHECKSUM: 41b6b69056545708f3956ded0b47fda298f175b8 +PODFILE CHECKSUM: e5cac55fd92698becba7d142dc17ec724cd6313f COCOAPODS: 1.16.2 diff --git a/Veloria.xcodeproj/project.pbxproj b/Veloria.xcodeproj/project.pbxproj index f6bd9d8..403eb7b 100644 --- a/Veloria.xcodeproj/project.pbxproj +++ b/Veloria.xcodeproj/project.pbxproj @@ -33,6 +33,36 @@ 1B056E772DDB3641007EE38D /* VPTabBarItemNormalVew.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B056E762DDB3641007EE38D /* VPTabBarItemNormalVew.swift */; }; 1B056E792DDB365A007EE38D /* VPTabBarItemSelectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B056E782DDB365A007EE38D /* VPTabBarItemSelectedView.swift */; }; 1B056E7B2DDB37BA007EE38D /* VPGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B056E7A2DDB37BA007EE38D /* VPGradientView.swift */; }; + BF0FA6D52DDC5B5D00C9E5F2 /* VPApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6D42DDC5B5D00C9E5F2 /* VPApi.swift */; }; + BF0FA6D72DDC5BE100C9E5F2 /* VPURLPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6D62DDC5BE100C9E5F2 /* VPURLPath.swift */; }; + BF0FA6DA2DDC5CB600C9E5F2 /* VPLoginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6D92DDC5CB600C9E5F2 /* VPLoginManager.swift */; }; + BF0FA6DC2DDC5CD700C9E5F2 /* VPTokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6DB2DDC5CD700C9E5F2 /* VPTokenModel.swift */; }; + BF0FA6DF2DDC5E4D00C9E5F2 /* String+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6DE2DDC5E4D00C9E5F2 /* String+VPAdd.swift */; }; + BF0FA6EE2DDC5F8700C9E5F2 /* JXUUID.m in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6EA2DDC5F8700C9E5F2 /* JXUUID.m */; }; + BF0FA6EF2DDC5F8700C9E5F2 /* PDKeyChain.m in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6EC2DDC5F8700C9E5F2 /* PDKeyChain.m */; }; + BF0FA6F12DDC600200C9E5F2 /* VPNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6F02DDC600200C9E5F2 /* VPNetwork.swift */; }; + BF0FA6F42DDC604500C9E5F2 /* VPHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6F32DDC604500C9E5F2 /* VPHUD.swift */; }; + BF0FA6F62DDC616300C9E5F2 /* VPToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6F52DDC616300C9E5F2 /* VPToast.swift */; }; + BF0FA6F92DDC64E700C9E5F2 /* VPHomeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6F82DDC64E700C9E5F2 /* VPHomeAPI.swift */; }; + BF0FA6FC2DDC657500C9E5F2 /* VPHomeDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6FB2DDC657500C9E5F2 /* VPHomeDataModel.swift */; }; + BF0FA7002DDC665300C9E5F2 /* VPShortModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA6FF2DDC665300C9E5F2 /* VPShortModel.swift */; }; + BF0FA7022DDC667C00C9E5F2 /* VPVideoInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7012DDC667C00C9E5F2 /* VPVideoInfoModel.swift */; }; + BF0FA7052DDC67AC00C9E5F2 /* VPHomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7042DDC67AC00C9E5F2 /* VPHomeViewModel.swift */; }; + BF0FA7072DDC687C00C9E5F2 /* VPHomeDataItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7062DDC687C00C9E5F2 /* VPHomeDataItem.swift */; }; + BF0FA70A2DDC69C800C9E5F2 /* VPTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7092DDC69C800C9E5F2 /* VPTableViewCell.swift */; }; + BF0FA70C2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA70B2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift */; }; + BF0FA70E2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA70D2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift */; }; + BF0FA7102DDC6CA200C9E5F2 /* VPListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA70F2DDC6CA200C9E5F2 /* VPListModel.swift */; }; + BF0FA7122DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7112DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift */; }; + BF0FA7162DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7142DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift */; }; + BF0FA7172DDC78FF00C9E5F2 /* ZKCycleScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7132DDC78FF00C9E5F2 /* ZKCycleScrollView.swift */; }; + BF0FA7192DDC7F4900C9E5F2 /* VPHomeBannerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7182DDC7F4900C9E5F2 /* VPHomeBannerCell.swift */; }; + BF0FA71B2DDC7FF200C9E5F2 /* VPImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA71A2DDC7FF200C9E5F2 /* VPImageView.swift */; }; + BF0FA71D2DDC807200C9E5F2 /* UIImageView+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA71C2DDC807200C9E5F2 /* UIImageView+VPAdd.swift */; }; + BF0FA7202DDC83AE00C9E5F2 /* JXButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA71E2DDC83AE00C9E5F2 /* JXButton.swift */; }; + BF0FA7222DDC859D00C9E5F2 /* NSNumber+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7212DDC859D00C9E5F2 /* NSNumber+VPAdd.swift */; }; + BF0FA7242DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7232DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift */; }; + BF0FA7262DDC8F7600C9E5F2 /* VPCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */; }; F939C04AD4003BA127F15C28 /* Pods_Veloria.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */; }; /* End PBXBuildFile section */ @@ -70,6 +100,38 @@ 34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Veloria.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 831DCE145EAEF22088BB06E4 /* Pods-Veloria.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Veloria.debug.xcconfig"; path = "Target Support Files/Pods-Veloria/Pods-Veloria.debug.xcconfig"; sourceTree = ""; }; AA827CC8ACC3AE70E33A0EEB /* Pods-Veloria.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Veloria.release.xcconfig"; path = "Target Support Files/Pods-Veloria/Pods-Veloria.release.xcconfig"; sourceTree = ""; }; + BF0FA6D42DDC5B5D00C9E5F2 /* VPApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPApi.swift; sourceTree = ""; }; + BF0FA6D62DDC5BE100C9E5F2 /* VPURLPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPURLPath.swift; sourceTree = ""; }; + BF0FA6D92DDC5CB600C9E5F2 /* VPLoginManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPLoginManager.swift; sourceTree = ""; }; + BF0FA6DB2DDC5CD700C9E5F2 /* VPTokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPTokenModel.swift; sourceTree = ""; }; + BF0FA6DE2DDC5E4D00C9E5F2 /* String+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+VPAdd.swift"; sourceTree = ""; }; + BF0FA6E92DDC5F8700C9E5F2 /* JXUUID.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JXUUID.h; sourceTree = ""; }; + BF0FA6EA2DDC5F8700C9E5F2 /* JXUUID.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JXUUID.m; sourceTree = ""; }; + BF0FA6EB2DDC5F8700C9E5F2 /* PDKeyChain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PDKeyChain.h; sourceTree = ""; }; + BF0FA6EC2DDC5F8700C9E5F2 /* PDKeyChain.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PDKeyChain.m; sourceTree = ""; }; + BF0FA6F02DDC600200C9E5F2 /* VPNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNetwork.swift; sourceTree = ""; }; + BF0FA6F32DDC604500C9E5F2 /* VPHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHUD.swift; sourceTree = ""; }; + BF0FA6F52DDC616300C9E5F2 /* VPToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPToast.swift; sourceTree = ""; }; + BF0FA6F82DDC64E700C9E5F2 /* VPHomeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeAPI.swift; sourceTree = ""; }; + BF0FA6FB2DDC657500C9E5F2 /* VPHomeDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeDataModel.swift; sourceTree = ""; }; + BF0FA6FF2DDC665300C9E5F2 /* VPShortModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPShortModel.swift; sourceTree = ""; }; + BF0FA7012DDC667C00C9E5F2 /* VPVideoInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPVideoInfoModel.swift; sourceTree = ""; }; + BF0FA7042DDC67AC00C9E5F2 /* VPHomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeViewModel.swift; sourceTree = ""; }; + BF0FA7062DDC687C00C9E5F2 /* VPHomeDataItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeDataItem.swift; sourceTree = ""; }; + BF0FA7092DDC69C800C9E5F2 /* VPTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPTableViewCell.swift; sourceTree = ""; }; + BF0FA70B2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeBannerContentCell.swift; sourceTree = ""; }; + BF0FA70D2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeItemContentCell.swift; sourceTree = ""; }; + BF0FA70F2DDC6CA200C9E5F2 /* VPListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPListModel.swift; sourceTree = ""; }; + BF0FA7112DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeModuleItem.swift; sourceTree = ""; }; + BF0FA7132DDC78FF00C9E5F2 /* ZKCycleScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZKCycleScrollView.swift; sourceTree = ""; }; + BF0FA7142DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZKCycleScrollViewFlowLayout.swift; sourceTree = ""; }; + BF0FA7182DDC7F4900C9E5F2 /* VPHomeBannerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeBannerCell.swift; sourceTree = ""; }; + BF0FA71A2DDC7FF200C9E5F2 /* VPImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPImageView.swift; sourceTree = ""; }; + BF0FA71C2DDC807200C9E5F2 /* UIImageView+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageView+VPAdd.swift"; sourceTree = ""; }; + BF0FA71E2DDC83AE00C9E5F2 /* JXButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JXButton.swift; sourceTree = ""; }; + BF0FA7212DDC859D00C9E5F2 /* NSNumber+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNumber+VPAdd.swift"; sourceTree = ""; }; + BF0FA7232DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPHomeRecommandContentCell.swift; sourceTree = ""; }; + BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPCollectionView.swift; sourceTree = ""; }; E0BDA3570E00C90877E45AA0 /* Pods-VideoPlayer.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VideoPlayer.debug.xcconfig"; path = "Target Support Files/Pods-VideoPlayer/Pods-VideoPlayer.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -110,7 +172,7 @@ 1B056E372DDAC1EF007EE38D /* Base */, 1B056E362DDAC1E8007EE38D /* Class */, 1B056E352DDAC1DE007EE38D /* Libs */, - 1B056E342DDAC1C7007EE38D /* Thirdparty */, + BF0FA6E82DDC5F6F00C9E5F2 /* Thirdparty */, 1B056E332DDAC1AB007EE38D /* Source */, ); path = Veloria; @@ -137,16 +199,11 @@ path = Source; sourceTree = ""; }; - 1B056E342DDAC1C7007EE38D /* Thirdparty */ = { - isa = PBXGroup; - children = ( - ); - path = Thirdparty; - sourceTree = ""; - }; 1B056E352DDAC1DE007EE38D /* Libs */ = { isa = PBXGroup; children = ( + BF0FA6F22DDC603200C9E5F2 /* HUD */, + BF0FA6D82DDC5C8200C9E5F2 /* Login */, 1B056E652DDAD098007EE38D /* LocalizedManager */, 1B056E472DDAC3CB007EE38D /* AppTool */, ); @@ -157,6 +214,7 @@ isa = PBXGroup; children = ( 1B056E522DDACBF4007EE38D /* Home */, + BF0FA6FD2DDC65F300C9E5F2 /* Player */, ); path = Class; sourceTree = ""; @@ -189,6 +247,9 @@ children = ( 1B056E752DDB35C5007EE38D /* TabBar */, 1B056E7A2DDB37BA007EE38D /* VPGradientView.swift */, + BF0FA7092DDC69C800C9E5F2 /* VPTableViewCell.swift */, + BF0FA71A2DDC7FF200C9E5F2 /* VPImageView.swift */, + BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */, ); path = View; sourceTree = ""; @@ -197,6 +258,7 @@ isa = PBXGroup; children = ( 1B056E402DDAC30A007EE38D /* VPModel.swift */, + BF0FA70F2DDC6CA200C9E5F2 /* VPListModel.swift */, ); path = Model; sourceTree = ""; @@ -204,6 +266,7 @@ 1B056E3B2DDAC22D007EE38D /* Networking */ = { isa = PBXGroup; children = ( + BF0FA6F72DDC64CF00C9E5F2 /* API */, 1B056E3D2DDAC283007EE38D /* Base */, ); path = Networking; @@ -221,6 +284,9 @@ isa = PBXGroup; children = ( 1B056E3E2DDAC2DB007EE38D /* VPCryptorService.swift */, + BF0FA6D42DDC5B5D00C9E5F2 /* VPApi.swift */, + BF0FA6D62DDC5BE100C9E5F2 /* VPURLPath.swift */, + BF0FA6F02DDC600200C9E5F2 /* VPNetwork.swift */, ); path = Base; sourceTree = ""; @@ -233,6 +299,9 @@ 1B056E5A2DDACD80007EE38D /* UIColor+VPAdd.swift */, 1B056E5C2DDACD8E007EE38D /* UIFont+VPAdd.swift */, 1B056E732DDB2DD7007EE38D /* UIView+VPAdd.swift */, + BF0FA6DE2DDC5E4D00C9E5F2 /* String+VPAdd.swift */, + BF0FA71C2DDC807200C9E5F2 /* UIImageView+VPAdd.swift */, + BF0FA7212DDC859D00C9E5F2 /* NSNumber+VPAdd.swift */, ); path = Extension; sourceTree = ""; @@ -249,26 +318,13 @@ isa = PBXGroup; children = ( 1B056E552DDACC08007EE38D /* Controller */, - 1B056E542DDACC03007EE38D /* View */, - 1B056E532DDACBFA007EE38D /* Model */, + BF0FA7082DDC699A00C9E5F2 /* View */, + BF0FA6FA2DDC652000C9E5F2 /* Model */, + BF0FA7032DDC679B00C9E5F2 /* ViewModel */, ); path = Home; sourceTree = ""; }; - 1B056E532DDACBFA007EE38D /* Model */ = { - isa = PBXGroup; - children = ( - ); - path = Model; - sourceTree = ""; - }; - 1B056E542DDACC03007EE38D /* View */ = { - isa = PBXGroup; - children = ( - ); - path = View; - sourceTree = ""; - }; 1B056E552DDACC08007EE38D /* Controller */ = { isa = PBXGroup; children = ( @@ -317,6 +373,116 @@ name = Frameworks; sourceTree = ""; }; + BF0FA6D82DDC5C8200C9E5F2 /* Login */ = { + isa = PBXGroup; + children = ( + BF0FA6D92DDC5CB600C9E5F2 /* VPLoginManager.swift */, + BF0FA6DB2DDC5CD700C9E5F2 /* VPTokenModel.swift */, + ); + path = Login; + sourceTree = ""; + }; + BF0FA6E82DDC5F6F00C9E5F2 /* Thirdparty */ = { + isa = PBXGroup; + children = ( + BF0FA6ED2DDC5F8700C9E5F2 /* JXUUID */, + BF0FA7152DDC78FF00C9E5F2 /* ZKCycleScrollView-Swift */, + BF0FA71F2DDC83AE00C9E5F2 /* JXButton */, + ); + path = Thirdparty; + sourceTree = ""; + }; + BF0FA6ED2DDC5F8700C9E5F2 /* JXUUID */ = { + isa = PBXGroup; + children = ( + BF0FA6E92DDC5F8700C9E5F2 /* JXUUID.h */, + BF0FA6EA2DDC5F8700C9E5F2 /* JXUUID.m */, + BF0FA6EB2DDC5F8700C9E5F2 /* PDKeyChain.h */, + BF0FA6EC2DDC5F8700C9E5F2 /* PDKeyChain.m */, + ); + path = JXUUID; + sourceTree = ""; + }; + BF0FA6F22DDC603200C9E5F2 /* HUD */ = { + isa = PBXGroup; + children = ( + BF0FA6F32DDC604500C9E5F2 /* VPHUD.swift */, + BF0FA6F52DDC616300C9E5F2 /* VPToast.swift */, + ); + path = HUD; + sourceTree = ""; + }; + BF0FA6F72DDC64CF00C9E5F2 /* API */ = { + isa = PBXGroup; + children = ( + BF0FA6F82DDC64E700C9E5F2 /* VPHomeAPI.swift */, + ); + path = API; + sourceTree = ""; + }; + BF0FA6FA2DDC652000C9E5F2 /* Model */ = { + isa = PBXGroup; + children = ( + BF0FA6FB2DDC657500C9E5F2 /* VPHomeDataModel.swift */, + BF0FA7062DDC687C00C9E5F2 /* VPHomeDataItem.swift */, + BF0FA7112DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift */, + ); + path = Model; + sourceTree = ""; + }; + BF0FA6FD2DDC65F300C9E5F2 /* Player */ = { + isa = PBXGroup; + children = ( + BF0FA6FE2DDC660300C9E5F2 /* Model */, + ); + path = Player; + sourceTree = ""; + }; + BF0FA6FE2DDC660300C9E5F2 /* Model */ = { + isa = PBXGroup; + children = ( + BF0FA6FF2DDC665300C9E5F2 /* VPShortModel.swift */, + BF0FA7012DDC667C00C9E5F2 /* VPVideoInfoModel.swift */, + ); + path = Model; + sourceTree = ""; + }; + BF0FA7032DDC679B00C9E5F2 /* ViewModel */ = { + isa = PBXGroup; + children = ( + BF0FA7042DDC67AC00C9E5F2 /* VPHomeViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + BF0FA7082DDC699A00C9E5F2 /* View */ = { + isa = PBXGroup; + children = ( + BF0FA70D2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift */, + BF0FA70B2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift */, + BF0FA7232DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift */, + BF0FA7182DDC7F4900C9E5F2 /* VPHomeBannerCell.swift */, + ); + path = View; + sourceTree = ""; + }; + BF0FA7152DDC78FF00C9E5F2 /* ZKCycleScrollView-Swift */ = { + isa = PBXGroup; + children = ( + BF0FA7132DDC78FF00C9E5F2 /* ZKCycleScrollView.swift */, + BF0FA7142DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift */, + ); + path = "ZKCycleScrollView-Swift"; + sourceTree = ""; + }; + BF0FA71F2DDC83AE00C9E5F2 /* JXButton */ = { + isa = PBXGroup; + children = ( + BF0FA71E2DDC83AE00C9E5F2 /* JXButton.swift */, + ); + path = JXButton; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -436,8 +602,14 @@ buildActionMask = 2147483647; files = ( 1B056E742DDB2DD7007EE38D /* UIView+VPAdd.swift in Sources */, + BF0FA6F12DDC600200C9E5F2 /* VPNetwork.swift in Sources */, 1B056E572DDACC6B007EE38D /* VPHomePageViewController.swift in Sources */, + BF0FA6DF2DDC5E4D00C9E5F2 /* String+VPAdd.swift in Sources */, + BF0FA7002DDC665300C9E5F2 /* VPShortModel.swift in Sources */, 1B056E7B2DDB37BA007EE38D /* VPGradientView.swift in Sources */, + BF0FA7262DDC8F7600C9E5F2 /* VPCollectionView.swift in Sources */, + BF0FA71D2DDC807200C9E5F2 /* UIImageView+VPAdd.swift in Sources */, + BF0FA6DC2DDC5CD700C9E5F2 /* VPTokenModel.swift in Sources */, 1B056E772DDB3641007EE38D /* VPTabBarItemNormalVew.swift in Sources */, 1B056E4B2DDAC6BA007EE38D /* VPDefine.swift in Sources */, 1B056E702DDB019B007EE38D /* VPTabBarItemContainer.swift in Sources */, @@ -445,19 +617,43 @@ 1B056E2B2DDAC0FD007EE38D /* AppDelegate.swift in Sources */, 1B056E6C2DDADAA1007EE38D /* VPTabBar.swift in Sources */, 1B056E5D2DDACD8E007EE38D /* UIFont+VPAdd.swift in Sources */, + BF0FA6F92DDC64E700C9E5F2 /* VPHomeAPI.swift in Sources */, + BF0FA6F42DDC604500C9E5F2 /* VPHUD.swift in Sources */, + BF0FA7222DDC859D00C9E5F2 /* NSNumber+VPAdd.swift in Sources */, + BF0FA71B2DDC7FF200C9E5F2 /* VPImageView.swift in Sources */, 1B056E2C2DDAC0FD007EE38D /* SceneDelegate.swift in Sources */, 1B056E462DDAC370007EE38D /* UIScreen+VPAdd.swift in Sources */, + BF0FA6EE2DDC5F8700C9E5F2 /* JXUUID.m in Sources */, + BF0FA7052DDC67AC00C9E5F2 /* VPHomeViewModel.swift in Sources */, + BF0FA6EF2DDC5F8700C9E5F2 /* PDKeyChain.m in Sources */, 1B056E4D2DDAC7C1007EE38D /* VPTabBarController.swift in Sources */, + BF0FA6DA2DDC5CB600C9E5F2 /* VPLoginManager.swift in Sources */, 1B056E4F2DDAC7FC007EE38D /* VPNavigationController.swift in Sources */, 1B056E3F2DDAC2DB007EE38D /* VPCryptorService.swift in Sources */, + BF0FA7202DDC83AE00C9E5F2 /* JXButton.swift in Sources */, + BF0FA7072DDC687C00C9E5F2 /* VPHomeDataItem.swift in Sources */, + BF0FA70E2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift in Sources */, + BF0FA6D72DDC5BE100C9E5F2 /* VPURLPath.swift in Sources */, 1B056E5B2DDACD80007EE38D /* UIColor+VPAdd.swift in Sources */, 1B056E6A2DDAD0BF007EE38D /* VPLocalizedManager.swift in Sources */, + BF0FA7242DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift in Sources */, + BF0FA7192DDC7F4900C9E5F2 /* VPHomeBannerCell.swift in Sources */, + BF0FA7022DDC667C00C9E5F2 /* VPVideoInfoModel.swift in Sources */, 1B056E792DDB365A007EE38D /* VPTabBarItemSelectedView.swift in Sources */, 1B056E722DDB022F007EE38D /* VPTabBarItem.swift in Sources */, 1B056E412DDAC30A007EE38D /* VPModel.swift in Sources */, + BF0FA70A2DDC69C800C9E5F2 /* VPTableViewCell.swift in Sources */, 1B056E442DDAC355007EE38D /* UIDevice+VPAdd.swift in Sources */, 1B056E512DDACBE5007EE38D /* VPViewController.swift in Sources */, + BF0FA7122DDC6D2C00C9E5F2 /* VPHomeModuleItem.swift in Sources */, + BF0FA7162DDC78FF00C9E5F2 /* ZKCycleScrollViewFlowLayout.swift in Sources */, + BF0FA7172DDC78FF00C9E5F2 /* ZKCycleScrollView.swift in Sources */, + BF0FA6D52DDC5B5D00C9E5F2 /* VPApi.swift in Sources */, + BF0FA6FC2DDC657500C9E5F2 /* VPHomeDataModel.swift in Sources */, + BF0FA6F62DDC616300C9E5F2 /* VPToast.swift in Sources */, 1B056E492DDAC3DF007EE38D /* VPAppTool.swift in Sources */, + BF0FA70C2DDC6A3800C9E5F2 /* VPHomeBannerContentCell.swift in Sources */, + BF0FA7102DDC6CA200C9E5F2 /* VPListModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Veloria/Base/Controller/VPTabBarController.swift b/Veloria/Base/Controller/VPTabBarController.swift index fa29331..0e901e7 100644 --- a/Veloria/Base/Controller/VPTabBarController.swift +++ b/Veloria/Base/Controller/VPTabBarController.swift @@ -51,9 +51,11 @@ class VPTabBarController: UITabBarController { tabBar.delegate = self self.setValue(tabBar, forKey: "tabBar") + view.backgroundColor = .color000000() + // ESTabBarController vp_setup() -// self.tabBar.isTranslucent = false + self.tabBar.isTranslucent = true } @@ -75,9 +77,10 @@ extension VPTabBarController { let appearance = UITabBarAppearance() appearance.backgroundImage = UIImage(color: .clear) - appearance.backgroundColor = .red + appearance.backgroundColor = .colorFFFFFF(alpha: 0.1) appearance.shadowImage = UIImage() appearance.shadowColor = .clear + appearance.backgroundEffect = UIBlurEffect(style: .dark) tabBar.standardAppearance = appearance tabBar.scrollEdgeAppearance = appearance diff --git a/Veloria/Base/Controller/VPViewController.swift b/Veloria/Base/Controller/VPViewController.swift index f25dfe0..a764272 100644 --- a/Veloria/Base/Controller/VPViewController.swift +++ b/Veloria/Base/Controller/VPViewController.swift @@ -8,12 +8,23 @@ import UIKit class VPViewController: UIViewController { - + + private(set) lazy var bgImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "bg_image_01")) + return imageView + }() + override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .backgroundColor() + + view.addSubview(bgImageView) + + bgImageView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } } diff --git a/Veloria/Base/Define/VPDefine.swift b/Veloria/Base/Define/VPDefine.swift index cf7e350..d9d23b6 100644 --- a/Veloria/Base/Define/VPDefine.swift +++ b/Veloria/Base/Define/VPDefine.swift @@ -8,6 +8,18 @@ import UIKit +//MARK:-------------- 系统版本号 -------------- +///当前系统版本号 +let kVPOsVersion: String = UIDevice.current.systemVersion +let kVPAPPBundleIdentifier: String = (Bundle.main.infoDictionary!["CFBundleIdentifier"] as? String) ?? "0" + +///app版本号 +public let kVPAPPVersion: String = (Bundle.main.infoDictionary!["CFBundleShortVersionString"] as? String) ?? "0" +public let kVPAPPBundleVersion: String = (Bundle.main.infoDictionary!["CFBundleVersion"] as? String) ?? "0" + +public let kVPAPPBundleName: String = (Bundle.main.infoDictionary!["CFBundleName"] as? String) ?? "" +public let kVPAPPName: String = (Bundle.main.infoDictionary!["CFBundleDisplayName"] as? String) ?? "" + //MARK: ------- 打印信息 ---------- #if DEBUG diff --git a/Veloria/Base/Extension/NSNumber+VPAdd.swift b/Veloria/Base/Extension/NSNumber+VPAdd.swift new file mode 100644 index 0000000..3ab4489 --- /dev/null +++ b/Veloria/Base/Extension/NSNumber+VPAdd.swift @@ -0,0 +1,25 @@ +// +// NSNumber+VPAdd.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +extension NSNumber { + + func toString(maximumFractionDigits: Int = 10, minimumFractionDigits: Int? = nil, roundingMode: NumberFormatter.RoundingMode? = nil) -> String { + let formatter = NumberFormatter() + formatter.minimumIntegerDigits = 1 + formatter.maximumFractionDigits = maximumFractionDigits + if let minimumFractionDigits = minimumFractionDigits { + formatter.minimumFractionDigits = minimumFractionDigits + } + if let roundingMode = roundingMode { + formatter.roundingMode = roundingMode + } + formatter.numberStyle = .none + return formatter.string(from: self) ?? "0" + } +} diff --git a/Veloria/Base/Extension/String+VPAdd.swift b/Veloria/Base/Extension/String+VPAdd.swift new file mode 100644 index 0000000..fc26e2d --- /dev/null +++ b/Veloria/Base/Extension/String+VPAdd.swift @@ -0,0 +1,33 @@ +// +// String+VPAdd.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit +import SmartCodable + +extension String: SmartCodable { + func length() -> Int { + return self.ocString().length + } + + func ocString() -> NSString { + return self as NSString + } + + static func timeZone() -> String { + let timeZone = NSTimeZone.local as NSTimeZone + let timeZoneSecondsFromGMT = timeZone.secondsFromGMT / 3600 + return String(format: "GMT+0%d:00", timeZoneSecondsFromGMT) + } +} + +extension String { + ///获取文字Size + func size(font: UIFont, size: CGSize = CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(MAXFLOAT))) -> CGSize{ + let string: NSString = self as NSString + return string.size(for: font, size: size, mode: .byWordWrapping) + } +} diff --git a/Veloria/Base/Extension/UIColor+VPAdd.swift b/Veloria/Base/Extension/UIColor+VPAdd.swift index d9060aa..58eb98f 100644 --- a/Veloria/Base/Extension/UIColor+VPAdd.swift +++ b/Veloria/Base/Extension/UIColor+VPAdd.swift @@ -33,4 +33,12 @@ extension UIColor { static func color353537(alpha: CGFloat = 1) -> UIColor { return UIColor(rgb: 0x353537, alpha: alpha) } + + static func color828282(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0x828282, alpha: alpha) + } + + static func colorAFAFAF(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xAFAFAF, alpha: alpha) + } } diff --git a/Veloria/Base/Extension/UIDevice+VPAdd.swift b/Veloria/Base/Extension/UIDevice+VPAdd.swift index 3004d14..13c77fe 100644 --- a/Veloria/Base/Extension/UIDevice+VPAdd.swift +++ b/Veloria/Base/Extension/UIDevice+VPAdd.swift @@ -8,3 +8,67 @@ import UIKit +extension UIDevice { + + //http://theiphonewiki.com/wiki/Models + static func sp_machineModelName() -> String { + guard let machineModel = UIDevice.current.machineModel else { return "" } + let map = [ + "iPhone1,1" : "iPhone", + "iPhone1,2" : "iPhone 3G", + "iPhone2,1" : "iPhone 3GS", + "iPhone3,1" : "iPhone 4", + "iPhone3,2" : "iPhone 4", + "iPhone3,3" : "iPhone 4", + "iPhone4,1" : "iPhone4,1", + "iPhone5,1" : "iPhone 5", + "iPhone5,2" : "iPhone 5", + "iPhone5,3" : "iPhone 5c", + "iPhone5,4" : "iPhone 5c", + "iPhone6,1" : "iPhone 5s", + "iPhone6,2" : "iPhone 5s", + "iPhone7,2" : "iPhone 6", + "iPhone7,1" : "iPhone 6 Plus", + "iPhone8,1" : "iPhone 6s", + "iPhone8,2" : "iPhone 6s Plus", + "iPhone8,4" : "iPhone SE (1st generation)", + "iPhone9,1" : "iPhone 7", + "iPhone9,3" : "iPhone 7", + "iPhone9,2" : "iPhone 7 Plus", + "iPhone9,4" : "iPhone 7 Plus", + "iPhone10,1" : "iPhone 8", + "iPhone10,4" : "iPhone 8", + "iPhone10,2" : "iPhone 8 Plus", + "iPhone10,5" : "iPhone 8 Plus", + "iPhone10,3" : "iPhone X", + "iPhone10,6" : "iPhone X", + "iPhone11,8" : "iPhone XR", + "iPhone11,2" : "iPhone11,2", + "iPhone11,6" : "iPhone XS Max", + "iPhone11,4" : "iPhone XS Max", + "iPhone12,1" : "iPhone 11", + "iPhone12,3" : "iPhone 11 Pro", + "iPhone12,5" : "iPhone 11 Pro Max", + "iPhone12,8" : "iPhone SE (2nd generation)", + "iPhone13,1" : "iPhone 12 mini", + "iPhone13,2" : "iPhone13,2", + "iPhone13,3" : "iPhone 12 Pro", + "iPhone13,4" : "iPhone 12 Pro Max", + "iPhone14,4" : "iPhone 13 mini", + "iPhone14,5" : "iPhone 13", + "iPhone14,2" : "iPhone 13 Pro", + "iPhone14,3" : "iPhone 13 Pro Max", + "iPhone14,6" : "iPhone SE (3rd generation)", + "iPhone14,7" : "iPhone 14", + "iPhone14,8" : "iPhone 14 Plus", + "iPhone15,2" : "iPhone 14 Pro", + "iPhone15,3" : "iPhone 14 Pro Max", + ] + if let name = map[machineModel] { + return name + } + + return machineModel + } +} + diff --git a/Veloria/Base/Extension/UIImageView+VPAdd.swift b/Veloria/Base/Extension/UIImageView+VPAdd.swift new file mode 100644 index 0000000..010eb45 --- /dev/null +++ b/Veloria/Base/Extension/UIImageView+VPAdd.swift @@ -0,0 +1,24 @@ +// +// UIImageView+VPAdd.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit +import Kingfisher + +extension UIImageView { + func vp_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/Veloria/Base/Model/VPListModel.swift b/Veloria/Base/Model/VPListModel.swift new file mode 100644 index 0000000..4b940ce --- /dev/null +++ b/Veloria/Base/Model/VPListModel.swift @@ -0,0 +1,22 @@ +// +// VPListModel.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit +import SmartCodable + +class VPListModel: VPModel, SmartCodable { + var list: [T]? + var pagination: VPListPaginationModel? +} + + +class VPListPaginationModel: VPModel, SmartCodable { + var current_page: Int? + var page_size: Int? + var page_total: Int? + var total_size: Int? +} diff --git a/Veloria/Base/Model/VPModel.swift b/Veloria/Base/Model/VPModel.swift index 9d6f895..3d8d5b7 100644 --- a/Veloria/Base/Model/VPModel.swift +++ b/Veloria/Base/Model/VPModel.swift @@ -9,7 +9,7 @@ import UIKit class VPModel: NSObject { - override init() { + required override init() { super.init() } diff --git a/Veloria/Base/Networking/API/VPHomeAPI.swift b/Veloria/Base/Networking/API/VPHomeAPI.swift new file mode 100644 index 0000000..aa9a70e --- /dev/null +++ b/Veloria/Base/Networking/API/VPHomeAPI.swift @@ -0,0 +1,22 @@ +// +// VPHomeAPI.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPHomeAPI: NSObject { + + ///首页数据 + static func requestHomeData(completer: ((_ list: [VPHomeModuleItem]?) -> Void)?) { + var param = VPNetworkParameters(path: "/home/all-modules") + param.method = .get + + + VPNetwork.request(parameters: param) { (response: VPNetworkResponse>) in + completer?(response.data?.list) + } + } +} diff --git a/Veloria/Base/Networking/Base/VPApi.swift b/Veloria/Base/Networking/Base/VPApi.swift new file mode 100644 index 0000000..90310fe --- /dev/null +++ b/Veloria/Base/Networking/Base/VPApi.swift @@ -0,0 +1,116 @@ +// +// VPApi.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit +import Moya +import SmartCodable + +struct VPNetworkData { + var parameters: VPNetworkParameters? + var completion: ((_ response: VPNetworkResponse) -> Void)? +} + +struct VPNetworkParameters { + var baseURL: URL? + var parameters: [String : Any]? + var method: Moya.Method = .post + var path: String + var isLoding: Bool = false + var isToast: Bool = true +} + +struct VPNetworkResponse: SmartCodable { + var code: Int? + var data: T? + var msg: String? + + ///原始数据 + @IgnoredKey + var rawData: Any? +} + +enum VPApi { + case request(parameters: VPNetworkParameters) + case uploadFile(parameters: VPNetworkParameters) + case downloadFile(parameters: VPNetworkParameters) +} + +extension VPApi: TargetType { + + + var baseURL: URL { + return URL(string: VPBaseURL)! + } + + + var path: String { + switch self { + case .request(let parameters): + return VPURLPathPrefix + parameters.path + default: + return "" + } + } + + var method: Moya.Method { + switch self { + case .request(let parameters): + return parameters.method + default: + return .post + } + } + + var task: Moya.Task { + switch self { + case .request(let parameters): + let p = parameters.parameters ?? [:] + return .requestParameters(parameters: p, encoding: getEncoding()) + default: + return .requestParameters(parameters: [:], encoding: getEncoding()) + } + } + + var headers: [String : String]? { + let userToken = VPLoginManager.manager.token?.token ?? "" + + var dic: [String : String] = [ + "system-version" : kVPOsVersion, + "lang-key" : VPLocalizedManager.shared.currentLocalizedKey,//当前语言 + "time-zone" : String.timeZone(), //时区 + "app-version" : kVPAPPVersion, +// "device-id" : JXUUID.systemUUID(), //设备id + "device-id" : JXUUID.uuid(), //设备id + "brand" : "apple", //品牌 + "app-name" : "", + "system-type" : "ios", + "idfa" : JXUUID.idfa(), + "model" : UIDevice.sp_machineModelName(), +// "security" : "false", + ] + //登录信息 + dic["authorization"] = userToken + + + + return dic + } +} + +extension TargetType { + var sampleData: Data { return "".data(using: String.Encoding.utf8)! } + + func getEncoding() -> ParameterEncoding { + switch self.method { + case .get, .delete: + return URLEncoding.default + default: + return JSONEncoding.default + } + } + +} diff --git a/Veloria/Base/Networking/Base/VPNetwork.swift b/Veloria/Base/Networking/Base/VPNetwork.swift new file mode 100644 index 0000000..95a2690 --- /dev/null +++ b/Veloria/Base/Networking/Base/VPNetwork.swift @@ -0,0 +1,224 @@ +// +// VPNetwork.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit +import Moya +import SmartCodable + +///获取数据成功 +let VPNetworkCodeSucceed = 200 + +class VPNetwork: NSObject { + + ///用来获取token的线程 + private static let operationQueue = OperationQueue() + private static var tokenOperation: BlockOperation? + + static let provider = MoyaProvider(requestClosure: CustomApiTimeoutClosure) + + static func request(parameters: VPNetworkParameters, completion: ((_ response: VPNetworkResponse) -> Void)?) { + + if VPLoginManager.manager.token == nil { + self.requestToken(completer: nil) + } + + + if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" { + + let requestOperation = BlockOperation { + let semaphore = DispatchSemaphore(value: 0) + _request(parameters: parameters) { (response: VPNetworkResponse) in + semaphore.signal() + completion?(response) + } + semaphore.wait() + } + ///设置依赖关系 + requestOperation.addDependency(tokenOperation) + + operationQueue.addOperation(requestOperation) + } else { + _request(parameters: parameters, completion: completion) + } + } + + @discardableResult + static func _request(parameters: VPNetworkParameters, completion: ((_ response: VPNetworkResponse) -> Void)?) -> Cancellable { + + if parameters.isLoding { + VPHUD.show() + } + return provider.requestCustomJson(.request(parameters: parameters)) { (result) in + + if parameters.isLoding { + VPHUD.dismiss() + } + guard let completion = completion else {return} + + + _resultDispose(parameters: parameters, result: result, completion: completion) + } + } + + + private static func _resultDispose(parameters: VPNetworkParameters, result: Result, completion: ((_ response: VPNetworkResponse) -> Void)?) { + + switch result { + case .success(let response): + let code = response.statusCode + if code == 401 || code == 402 || code == 403 { + + if parameters.path == "/customer/register" { + var res = VPNetworkResponse() + res.code = -1 + if parameters.isToast { + VPToast.show(text: "movia_error".localized) + } + completion?(res) + } else { + ///重新获取token + self.requestToken(completer: nil) + + ///将请求失败数据重新请求 + if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" { + + let requestOperation = BlockOperation { + let semaphore = DispatchSemaphore(value: 0) + _request(parameters: parameters) { (response: VPNetworkResponse) in + semaphore.signal() + completion?(response) + } + semaphore.wait() + } + ///设置依赖关系 + requestOperation.addDependency(tokenOperation) + + operationQueue.addOperation(requestOperation) + } + } + + + return + } + + do { + let tempData = try response.mapString() + vpLog(message: parameters.parameters) + vpLog(message: parameters.path) + + + DispatchQueue.global().async { + let response: VPNetworkResponse = _deserialize(data: tempData) + + DispatchQueue.main.async { + if response.code != VPNetworkCodeSucceed { + if parameters.isToast { + VPToast.show(text: response.msg) + } + } + completion?(response) + } + } + + } catch { + var res = VPNetworkResponse() + res.code = -1 + if parameters.isToast { + VPToast.show(text: "movia_error".localized) + } + completion?(res) + } + case .failure(let error): + vpLog(message: error) + var res = VPNetworkResponse() + res.code = -1 + if parameters.isToast { + VPToast.show(text: "movia_network_toast_01".localized) + } + completion?(res) + break + } + + } + + ///解析数据 + static private func _deserialize(data: String) -> VPNetworkResponse { + var response: VPNetworkResponse? + + let time = Date().timeIntervalSince1970 + let decrypted = VPCryptorService.decrypt(data: data) + vpLog(message: decrypted) + response = VPNetworkResponse.deserialize(from: decrypted) + response?.rawData = decrypted + + vpLog(message: Date().timeIntervalSince1970 - time) + + if let response = response { + return response + } else { + var response = VPNetworkResponse() + response.code = -1 + response.msg = "movia_error".localized + return response + } + } + +} + +extension VPNetwork { + ///获取token + static func requestToken(completer: ((_ token: VPTokenModel?) -> Void)?) { + guard self.tokenOperation == nil else { + completer?(nil) + return + } + + self.tokenOperation = BlockOperation(block: { + let semaphore = DispatchSemaphore(value: 0) + let param = VPNetworkParameters(path: "/customer/register") + VPNetwork.request(parameters: param) { (response: VPNetworkResponse) in + if let token = response.data { + VPLoginManager.manager.setLoginToken(token: token) + } + do { semaphore.signal() } + self.tokenOperation = nil + completer?(response.data) + } + semaphore.wait() + }) + operationQueue.addOperation(self.tokenOperation!) + } + + +} + +extension MoyaProvider { + + @discardableResult + func requestCustomJson(_ target: Target, callbackQueue: DispatchQueue? = nil, completion: Completion?) -> Cancellable { + return request(target, callbackQueue: callbackQueue) { (result) in + guard let completion = completion else {return} + completion(result) + } + } +} + +let CustomApiTimeoutClosure = {(endpoint: Endpoint, closure: MoyaProvider.RequestResultClosure) -> Void in + + if var urlRequest = try? endpoint.urlRequest() { + ///总是获取新数据 + urlRequest.cachePolicy = .reloadIgnoringCacheData + urlRequest.timeoutInterval = 30 + closure(.success(urlRequest)) + } else { + closure(.failure(MoyaError.requestMapping(endpoint.url))) + } + + #if DEBUG /// + //print(try? endpoint.urlRequest() ) + #endif +} diff --git a/Veloria/Base/Networking/Base/VPURLPath.swift b/Veloria/Base/Networking/Base/VPURLPath.swift new file mode 100644 index 0000000..b3dc47f --- /dev/null +++ b/Veloria/Base/Networking/Base/VPURLPath.swift @@ -0,0 +1,46 @@ +// +// VPURLPath.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +#if DEBUG +let VPBaseURL = "https://api-qjwl168.qjwl168.com" +let VPURLPathPrefix = "/velo" + +let VPWebBaseURL = "https://www.thimratv.com" +let VPCampaignWebURL = "https://campaign.moviatv.com" +#else +let VPBaseURL = "https://api-qjwl168.qjwl168.com" +let VPURLPathPrefix = "/velo" + +let VPWebBaseURL = "https://www.thimratv.com" +let VPCampaignWebURL = "https://campaign.moviatv.com" +#endif + +///用户协议 +let VPUserAgreementWebUrl = VPWebBaseURL + "/user_policy" +///隐私协议 +let VPPrivacyPolicyWebUrl = VPWebBaseURL + "/private" +///儿童个人信息保护规则 +let VPInformationProtectionWebUrl = VPWebBaseURL + "/information_protection" +///第三方共享清单 +let VPInformationSharingWebUrl = VPWebBaseURL + "/information_sharing" +///收集个人信息明示清单 +let VPPersoInforDisclosureWebUrl = VPWebBaseURL + "/persoInfor_disclosure" +///全国青少年互联网文明公约 +let VPCivizatioConventionWebUrl = VPWebBaseURL + "/civizatio_convention" +///会员服务协议 +let VPMemberShipAgreement = VPWebBaseURL + "/member_ship_agreement" + +///反馈首页 +let VPFeedBackHomeWebUrl = VPCampaignWebURL + "/pages/leave/index" +///反馈列表 +let VPFeedBackListWebUrl = VPCampaignWebURL + "/pages/leave/list" +///反馈详情 +let VPFeedBackDetailWebUrl = VPCampaignWebURL + "/pages/leave/detail" +///活动页面 +let VPRewardsWebUrl = VPCampaignWebURL diff --git a/Veloria/Base/View/VPCollectionView.swift b/Veloria/Base/View/VPCollectionView.swift new file mode 100644 index 0000000..603382e --- /dev/null +++ b/Veloria/Base/View/VPCollectionView.swift @@ -0,0 +1,22 @@ +// +// VPCollectionView.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPCollectionView: 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/Veloria/Base/View/VPImageView.swift b/Veloria/Base/View/VPImageView.swift new file mode 100644 index 0000000..3054089 --- /dev/null +++ b/Veloria/Base/View/VPImageView.swift @@ -0,0 +1,54 @@ +// +// VPImageView.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPImageView: UIImageView { + + var placeholderColor = UIColor.gray + + 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) { + fatalError("init(coder:) has not been implemented") + } + + + func _init() { + self.contentMode = .scaleAspectFill + self.layer.masksToBounds = true + if image == nil { + self.backgroundColor = self.placeholderColor + } + } + + 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 + } + } + } + } + +} diff --git a/Veloria/Base/View/VPTableViewCell.swift b/Veloria/Base/View/VPTableViewCell.swift new file mode 100644 index 0000000..66aea11 --- /dev/null +++ b/Veloria/Base/View/VPTableViewCell.swift @@ -0,0 +1,43 @@ +// +// VPTableViewCell.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPTableViewCell: UITableViewCell { + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + self.layer.rasterizationScale = UIScreen.main.scale + self.layer.shouldRasterize = true + self.selectionStyle = .none + self.backgroundColor = .clear + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + 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 + } + +} + +extension UITableViewCell { + + var vp_tableView: UITableView? { + return self.value(forKey: "_tableView") as? UITableView + } +} diff --git a/Veloria/Class/Home/Controller/VPHomePageViewController.swift b/Veloria/Class/Home/Controller/VPHomePageViewController.swift index 5306f39..19a40d5 100644 --- a/Veloria/Class/Home/Controller/VPHomePageViewController.swift +++ b/Veloria/Class/Home/Controller/VPHomePageViewController.swift @@ -7,16 +7,132 @@ import UIKit -class VPHomePageViewController: UIViewController { +class VPHomePageViewController: VPViewController { + + + private lazy var viewModel = VPHomeViewModel() + + private lazy var pageParam: WMZPageParam = { + let param = WMZPageParam() + param.wTitleArr = ["All".localized, "Romance", "Revenge"] + param.wViewController = { [weak self] index in + return VPViewController() + } + param.wTopSuspension = true + //顶部可下拉 + param.wBounces = true + param.wMenuTitleColor = .color828282() + param.wMenuTitleSelectColor = .colorFFFFFF() + param.wMenuTitleUIFont = .fontRegular(ofSize: 12) + param.wMenuTitleSelectUIFont = .fontMedium(ofSize: 12) + param.wMenuBgColor = .clear + param.wBgColor = .clear + param.wCustomMenuTitle = { [weak self] buttons in + + vpLog(message: "++++++++++\(buttons ?? [])") + + } + + param.wCustomMenuSelectTitle = { buttons in + vpLog(message: "----------\(buttons ?? [])") + } + + param.wCustomNaviBarY = { _ in + return 0 + } + param.wCustomTabbarY = { _ in + return 0 + } + return param + }() + + private lazy var pageView: WMZPageView = { + let y = UIScreen.statusBarHeight + let width = UIScreen.width + let height = UIScreen.height - y + let pageView = WMZPageView(frame: CGRect(x: 0, y: y, width: width, height: height), param: pageParam, parentReponder: self) + pageView.param = pageParam + pageView.backgroundColor = .clear + pageView.downSc?.backgroundColor = .clear +// pageView.downSc?.delegate = self + pageView.downSc?.dataSource = self + pageView.downSc?.isScrollEnabled = true + pageView.downSc?.register(VPHomeItemContentCell.self, forCellReuseIdentifier: "cell") + pageView.downSc?.register(VPHomeBannerContentCell.self, forCellReuseIdentifier: VPHomeBannerContentCell.moduleKey.rawValue) + pageView.downSc?.register(VPHomeRecommandContentCell.self, forCellReuseIdentifier: VPHomeRecommandContentCell.moduleKey.rawValue) +// pageView.downSc?.rowHeight = UITableView.automaticDimension +// pageView.downSc?.estimatedRowHeight = 100 + + return pageView + }() + + override func viewDidLoad() { super.viewDidLoad() + requestHomeData() + + vp_setupUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + private func createView(title: String) -> WMZPageNaviBtn { + let button = WMZPageNaviBtn(type: .custom) + button.setTitle(title, for: .normal) + button.setTitleColor(.red, for: .normal) + button.setTitleColor(.green, for: .selected) + return button + } + +} + +extension VPHomePageViewController { + + private func vp_setupUI() { + + view.addSubview(pageView) } - - - - +} + +//MARK: -------------- UITableViewDataSource -------------- +extension VPHomePageViewController: UITableViewDataSource, UITableViewDelegate { + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let item = self.viewModel.newModuleList[indexPath.row] + + let cell = tableView.dequeueReusableCell(withIdentifier: item.module_key?.rawValue ?? "cell") as! VPHomeItemContentCell + cell.item = item + return cell + } + + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.viewModel.newModuleList.count + } + + + +} + +extension VPHomePageViewController { + + private func requestHomeData() { + + VPHomeAPI.requestHomeData { [weak self] list in + guard let self = self else { return } + if let list = list { + self.viewModel.oldModuleList = list + self.pageView.downSc?.reloadData() + } + } + + } + } diff --git a/Veloria/Class/Home/Model/VPHomeDataItem.swift b/Veloria/Class/Home/Model/VPHomeDataItem.swift new file mode 100644 index 0000000..3987ea6 --- /dev/null +++ b/Veloria/Class/Home/Model/VPHomeDataItem.swift @@ -0,0 +1,20 @@ +// +// VPHomeDataItem.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPHomeDataItem: VPModel { + enum ItemType: String { + case banner + } + + + var type: ItemType = .banner + var list: [VPShortModel]? + + +} diff --git a/Veloria/Class/Home/Model/VPHomeDataModel.swift b/Veloria/Class/Home/Model/VPHomeDataModel.swift new file mode 100644 index 0000000..9b70723 --- /dev/null +++ b/Veloria/Class/Home/Model/VPHomeDataModel.swift @@ -0,0 +1,27 @@ +// +// VPHomeDataModel.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit +import SmartCodable + +class VPHomeDataModel: VPModel, SmartCodable { + + + var bannerData: [VPShortModel]? + ///首页九宫格数据 + var nineSquare: VPNineSquareModel? + ///新剧列表 + var manualNewestRecommand: [VPShortModel]? + ///热门排行 + var hotData: [VPShortModel]? +} + +class VPNineSquareModel: VPModel, SmartCodable { + + var list: [VPShortModel]? + var title: String? +} diff --git a/Veloria/Class/Home/Model/VPHomeModuleItem.swift b/Veloria/Class/Home/Model/VPHomeModuleItem.swift new file mode 100644 index 0000000..b843322 --- /dev/null +++ b/Veloria/Class/Home/Model/VPHomeModuleItem.swift @@ -0,0 +1,38 @@ +// +// VPHomeModuleItem.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit +import SmartCodable + +class VPHomeModuleItem: VPModel, SmartCodable { + + enum ModuleKey: String, SmartCaseDefaultable { + case banner = "home_banner" + case v3_recommand = "home_v3_recommand" + } + + + var module_key: ModuleKey? + var title: String? + var list: [VPShortModel]? + + @SmartAny + var data: Any? + + + func didFinishMapping() { + if let data = data as? [[String : Any]] { + self.list = [VPShortModel].deserialize(from: data) + } else if let data = data as? [String : Any] { + title = data["title"] as? String + list = [VPShortModel].deserialize(from: data["list"] as? [[String : Any]]) + } + } + +} + + diff --git a/Veloria/Class/Home/View/VPHomeBannerCell.swift b/Veloria/Class/Home/View/VPHomeBannerCell.swift new file mode 100644 index 0000000..3bc3a6a --- /dev/null +++ b/Veloria/Class/Home/View/VPHomeBannerCell.swift @@ -0,0 +1,98 @@ +// +// VPHomeBannerCell.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPHomeBannerCell: ZKCycleScrollViewCell { + + var model: VPShortModel? { + didSet { + coverImageView.vp_setImage(url: model?.horizontally_img) + titleLabel.text = model?.name + + let watchCount = model?.watch_total ?? 0 + if watchCount > 1000 { + let numStr = NSNumber(floatLiteral: CGFloat(watchCount) / 1000).toString(maximumFractionDigits: 1) + hotView.setTitle("\(numStr)K", for: .normal) + } else { + hotView.setTitle("\(watchCount)", for: .normal) + } + } + } + + private lazy var coverImageView: VPImageView = { + let imageView = VPImageView() + return imageView + }() + + private lazy var bottomView: UIView = { + let view = VPGradientView() + view.locations = [0, 1] + view.colors = [UIColor.color000000(alpha: 0).cgColor, UIColor.color000000().cgColor] + view.startPoint = .init(x: 0.5, y: 0) + view.endPoint = .init(x: 0.5, y: 1) + return view + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .fontMedium(ofSize: 12) + label.textColor = .colorFFFFFF() + return label + }() + + private lazy var hotView: UIButton = { + let button = JXButton(type: .custom) + button.isUserInteractionEnabled = false + button.jx_font = .fontRegular(ofSize: 10) + button.setTitleColor(.colorAFAFAF(), for: .normal) + button.setImage(UIImage(named: "hot_icon_01"), for: .normal) + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + vp_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +extension VPHomeBannerCell { + + private func vp_setupUI() { + contentView.addSubview(coverImageView) + contentView.addSubview(bottomView) + contentView.addSubview(hotView) + contentView.addSubview(titleLabel) + + coverImageView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + bottomView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.height.equalTo(62) + } + + hotView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(10) + make.bottom.equalToSuperview().offset(-11) + } + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(10) + make.bottom.equalToSuperview().offset(-27) + make.right.lessThanOrEqualToSuperview().offset(-100) + } + } + +} diff --git a/Veloria/Class/Home/View/VPHomeBannerContentCell.swift b/Veloria/Class/Home/View/VPHomeBannerContentCell.swift new file mode 100644 index 0000000..3e6203c --- /dev/null +++ b/Veloria/Class/Home/View/VPHomeBannerContentCell.swift @@ -0,0 +1,74 @@ +// +// VPHomeBannerContentCell.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPHomeBannerContentCell: VPHomeItemContentCell { + + override class var moduleKey: VPHomeModuleItem.ModuleKey { + return .banner + } + + override var item: VPHomeModuleItem? { + didSet { + bannerView.reloadData() + } + } + + private lazy var bannerView: ZKCycleScrollView = { + let view = ZKCycleScrollView(frame: .zero, shouldInfiniteLoop: true) + view.delegate = self + view.dataSource = self + view.hidesPageControl = true + view.layer.cornerRadius = 10 + view.layer.masksToBounds = true + view.register(VPHomeBannerCell.self, forCellWithReuseIdentifier: "cell") + return view + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + vp_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension VPHomeBannerContentCell { + + private func vp_setupUI() { + containerView.addSubview(bannerView) + + bannerView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(15) + make.centerX.equalToSuperview() + make.top.equalToSuperview() + make.bottom.equalToSuperview() + make.height.equalTo(182) + } + + } + +} + +//MARK: -------------- ZKCycleScrollViewDataSource ZKCycleScrollViewDelegate -------------- +extension VPHomeBannerContentCell: ZKCycleScrollViewDataSource, ZKCycleScrollViewDelegate { + + func numberOfItems(in cycleScrollView: ZKCycleScrollView) -> Int { + return self.item?.list?.count ?? 0 + } + + func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, cellForItemAt index: Int) -> ZKCycleScrollViewCell { + let cell = cycleScrollView.dequeueReusableCell(withReuseIdentifier: "cell", for: index) as! VPHomeBannerCell + cell.model = self.item?.list?[index] + return cell + } +} diff --git a/Veloria/Class/Home/View/VPHomeItemContentCell.swift b/Veloria/Class/Home/View/VPHomeItemContentCell.swift new file mode 100644 index 0000000..4428097 --- /dev/null +++ b/Veloria/Class/Home/View/VPHomeItemContentCell.swift @@ -0,0 +1,39 @@ +// +// VPHomeItemContentCell.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPHomeItemContentCell: VPTableViewCell { + + class var moduleKey: VPHomeModuleItem.ModuleKey { + return .banner + } + + var item: VPHomeModuleItem? + + + private(set) lazy var containerView: UIView = { + let view = UIView() + return view + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + contentView.addSubview(containerView) + + containerView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalToSuperview().offset(-40) + } + + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Veloria/Class/Home/View/VPHomeRecommandContentCell.swift b/Veloria/Class/Home/View/VPHomeRecommandContentCell.swift new file mode 100644 index 0000000..625ae9e --- /dev/null +++ b/Veloria/Class/Home/View/VPHomeRecommandContentCell.swift @@ -0,0 +1,85 @@ +// +// VPHomeRecommandContentCell.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPHomeRecommandContentCell: VPHomeItemContentCell { + + override class var moduleKey: VPHomeModuleItem.ModuleKey { + return .v3_recommand + } + + override var item: VPHomeModuleItem? { + didSet { + titleLabel.text = item?.title + collectionView.reloadData() + } + } + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .fontMedium(ofSize: 15) + label.textColor = .colorFFFFFF() + return label + }() + + private lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + layout.itemSize = CGSize(width: 138, height: 152 + 30) + return layout + }() + + private lazy var collectionView: VPCollectionView = { + let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + return collectionView + }() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + vp_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension VPHomeRecommandContentCell { + + private func vp_setupUI() { + containerView.addSubview(titleLabel) + containerView.addSubview(collectionView) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(15) + make.top.equalToSuperview() + } + + collectionView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.bottom.equalToSuperview() + make.top.equalToSuperview().offset(32) + make.height.equalTo(collectionViewLayout.itemSize.height) + } + } + +} + +//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource -------------- +//extension VPHomeRecommandContentCell: UICollectionViewDelegate, UICollectionViewDataSource { +// +// func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { +// +// } +// +// func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { +// return self.item?.list?.count ?? 0 +// } +//} diff --git a/Veloria/Class/Home/ViewModel/VPHomeViewModel.swift b/Veloria/Class/Home/ViewModel/VPHomeViewModel.swift new file mode 100644 index 0000000..988a9b1 --- /dev/null +++ b/Veloria/Class/Home/ViewModel/VPHomeViewModel.swift @@ -0,0 +1,56 @@ +// +// VPHomeViewModel.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPHomeViewModel: VPModel { + + var homeDataModel: VPHomeDataModel? { + didSet { + updateDataList() + } + } + + ///原始模版列表 不可直接使用,需要过滤 + var oldModuleList: [VPHomeModuleItem]? { + didSet { + filterModuleData() + } + } + ///筛选后的数据 + private(set) lazy var newModuleList: [VPHomeModuleItem] = [] + + + private(set) lazy var homeDataList: [VPHomeDataItem] = [] + + + ///筛选模版数据 + private func filterModuleData() { + newModuleList.removeAll() + + oldModuleList?.forEach({ + if let key = $0.module_key { + if key == .banner { + newModuleList.insert($0, at: 0) + } else { + newModuleList.append($0) + } + } + }) + } + + private func updateDataList() { + homeDataList.removeAll() + + if let bannerData = homeDataModel?.bannerData { + let item = VPHomeDataItem() + item.type = .banner + item.list = bannerData + } + + } +} diff --git a/Veloria/Class/Player/Model/VPShortModel.swift b/Veloria/Class/Player/Model/VPShortModel.swift new file mode 100644 index 0000000..adf93ee --- /dev/null +++ b/Veloria/Class/Player/Model/VPShortModel.swift @@ -0,0 +1,52 @@ +// +// VPShortModel.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit +import SmartCodable + +class VPShortModel: VPModel, SmartCodable { + + enum TagType: String, SmartCaseDefaultable { + case hot = "hot" + case new = "new" + } + + var id: String? + var all_coins: String? + var buy_type: String? + var collect_total: Int? + var sp_description: String? + var episode_total: Int? + var horizontally_img: String? + var image_url: String? + var is_collect: Bool? + var name: String? + var process: String? + var search_click_total: String? + var short_id: String? + var short_play_id: String? + var short_play_video_id: String? + var tag_type: TagType? + var video_info: VPVideoInfoModel? + var category: [String]? + ///观看数 + var watch_total: Int? + var current_episode: String? + var video_url: String? + + @IgnoredKey + var titleAttributedString: NSAttributedString? + @IgnoredKey + var sp_isSelected: Bool? + + + static func mappingForKey() -> [SmartKeyTransformer]? { + return [ + CodingKeys.sp_description <--- ["description"] + ] + } +} diff --git a/Veloria/Class/Player/Model/VPVideoInfoModel.swift b/Veloria/Class/Player/Model/VPVideoInfoModel.swift new file mode 100644 index 0000000..19bfe33 --- /dev/null +++ b/Veloria/Class/Player/Model/VPVideoInfoModel.swift @@ -0,0 +1,30 @@ +// +// VPVideoInfoModel.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit +import SmartCodable + +class VPVideoInfoModel: VPModel, SmartCodable { + + var coins: Int? + var vip_coins: Int? + var episode: String? + var id: String? + var image_url: String? + ///1:会员,2: 非会员 + var is_vip: Int? + ///是否锁定,购买后解锁 + var is_lock: Bool? + var promise_view_ad: Int? +// var revolution: [] + var short_id: String? + var short_play_id: String? + var short_play_video_id: String? + var video_url: String? + ///播放进度,毫秒 + var play_seconds: Int? +} diff --git a/Veloria/Libs/AppTool/VPAppTool.swift b/Veloria/Libs/AppTool/VPAppTool.swift index f48e12d..30452a5 100644 --- a/Veloria/Libs/AppTool/VPAppTool.swift +++ b/Veloria/Libs/AppTool/VPAppTool.swift @@ -10,4 +10,51 @@ import UIKit class VPAppTool: NSObject { 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 + } + } +} + +extension VPAppTool { + + ///打开消息通知设置页面 + static func openApnsSetting() { + if #available(iOS 16.0, *) { + if let url = URL(string: UIApplication.openNotificationSettingsURLString) { + UIApplication.shared.open(url) + } + } else { + if let url = URL(string: UIApplication.openSettingsURLString) { + UIApplication.shared.open(url) + } + } + } } diff --git a/Veloria/Libs/HUD/VPHUD.swift b/Veloria/Libs/HUD/VPHUD.swift new file mode 100644 index 0000000..d4b7967 --- /dev/null +++ b/Veloria/Libs/HUD/VPHUD.swift @@ -0,0 +1,23 @@ +// +// VPHUD.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPHUD: NSObject { + static func config() { +// SVProgressHUD.setDefaultMaskType(.clear) + } + + static func show() { +// SVProgressHUD.setDefaultMaskType(.clear) +// SVProgressHUD.show() + } + + static func dismiss() { +// SVProgressHUD.dismiss() + } +} diff --git a/Veloria/Libs/HUD/VPToast.swift b/Veloria/Libs/HUD/VPToast.swift new file mode 100644 index 0000000..9bda3ed --- /dev/null +++ b/Veloria/Libs/HUD/VPToast.swift @@ -0,0 +1,22 @@ +// +// VPToast.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPToast: NSObject { + + static func config() { + CSToastManager.setTapToDismissEnabled(false) + CSToastManager.setDefaultDuration(2) + CSToastManager.setDefaultPosition(CSToastPositionCenter) + } + + static func show(text: String?) { + guard let text = text else { return } +// SPAPPTool.getKeyWindow()?.makeToast(text) + } +} diff --git a/Veloria/Libs/Login/VPLoginManager.swift b/Veloria/Libs/Login/VPLoginManager.swift new file mode 100644 index 0000000..5e0b8c6 --- /dev/null +++ b/Veloria/Libs/Login/VPLoginManager.swift @@ -0,0 +1,25 @@ +// +// VPLoginManager.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit + +class VPLoginManager: NSObject { + + + static let manager = VPLoginManager() + + private(set) var token: VPTokenModel? + + ///是否正在刷新token + private(set) var isRefreshingToken = false + + + func setLoginToken(token: VPTokenModel?) { + self.token = token +// UserDefaults.jx_setObject(token, forKey: kSPLoginTokenDefaultsKey) + } +} diff --git a/Veloria/Libs/Login/VPTokenModel.swift b/Veloria/Libs/Login/VPTokenModel.swift new file mode 100644 index 0000000..4d15e84 --- /dev/null +++ b/Veloria/Libs/Login/VPTokenModel.swift @@ -0,0 +1,41 @@ +// +// VPTokenModel.swift +// Veloria +// +// Created by Veloria on 2025/5/20. +// + +import UIKit +import SmartCodable + +class VPTokenModel: VPModel, SmartCodable, NSSecureCoding { + + var token: String? + var customer_id: String? + var auto_login: Int? + + required init() { } + + static var supportsSecureCoding: Bool { + get { + 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/Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Contents.json b/Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Contents.json new file mode 100644 index 0000000..5c4d3b1 --- /dev/null +++ b/Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Frame@2x.png b/Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Frame@2x.png new file mode 100644 index 0000000..5146439 Binary files /dev/null and b/Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Frame@2x.png differ diff --git a/Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Frame@3x.png b/Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Frame@3x.png new file mode 100644 index 0000000..9deca96 Binary files /dev/null and b/Veloria/Source/Assets.xcassets/icon/hot_icon_01.imageset/Frame@3x.png differ diff --git a/Veloria/Source/Assets.xcassets/image/Contents.json b/Veloria/Source/Assets.xcassets/image/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Veloria/Source/Assets.xcassets/image/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/Contents.json b/Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/Contents.json new file mode 100644 index 0000000..1057d71 --- /dev/null +++ b/Veloria/Source/Assets.xcassets/image/bg_image_01.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/Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/背景@2x.png b/Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/背景@2x.png new file mode 100644 index 0000000..617e8f8 Binary files /dev/null and b/Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/背景@2x.png differ diff --git a/Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/背景@3x.png b/Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/背景@3x.png new file mode 100644 index 0000000..3178d8f Binary files /dev/null and b/Veloria/Source/Assets.xcassets/image/bg_image_01.imageset/背景@3x.png differ diff --git a/Veloria/Source/Veloria-Bridging-Header.h b/Veloria/Source/Veloria-Bridging-Header.h index 437f1c3..1e816d2 100644 --- a/Veloria/Source/Veloria-Bridging-Header.h +++ b/Veloria/Source/Veloria-Bridging-Header.h @@ -3,3 +3,6 @@ // #import +#import +#import "JXUUID.h" +#import diff --git a/Veloria/Source/en.lproj/Localizable.strings b/Veloria/Source/en.lproj/Localizable.strings index 8db4c52..ca69445 100644 --- a/Veloria/Source/en.lproj/Localizable.strings +++ b/Veloria/Source/en.lproj/Localizable.strings @@ -7,3 +7,4 @@ */ "Home" = "Home"; +"All" = "All"; diff --git a/Veloria/Thirdparty/JXButton/JXButton.swift b/Veloria/Thirdparty/JXButton/JXButton.swift new file mode 100644 index 0000000..c252be2 --- /dev/null +++ b/Veloria/Thirdparty/JXButton/JXButton.swift @@ -0,0 +1,252 @@ +// +// JXButton.swift +// BoJia +// +// Created by 火山传媒 on 2024/5/31. +// + +import UIKit + +class JXButton: UIButton { + lazy var jx_font: UIFont? = self.titleLabel?.font { + didSet { + self.titleLabel?.font = jx_font + } + } + + var maxTitleWidth: CGFloat = 0 + var titleDirection: UITextLayoutDirection? + ///左右边距 + var leftAndRightMargin: CGFloat = 0 + + ///文字与图片的间距 + var space: CGFloat = 0 + + private var imageRect: CGRect = .zero + private var titleRect: CGRect = .zero + + + override class var layerClass: AnyClass { + return CAGradientLayer.self + } + + var gradientLayer: CAGradientLayer { + return self.layer as! CAGradientLayer + } + + var locations: [NSNumber]? { + didSet { + self.gradientLayer.locations = locations + } + } + + var colors: [CGColor]? { + didSet { + self.gradientLayer.colors = colors + } + } + + var startPoint: CGPoint = .zero { + didSet { + self.gradientLayer.startPoint = startPoint + } + } + + var endPoint: CGPoint = .zero { + didSet { + self.gradientLayer.endPoint = endPoint + } + } + + override var intrinsicContentSize: CGSize { + + var width: CGFloat = 0 + var height: CGFloat = 0 + switch titleDirection { + case .left: + width = imageRect.width + titleRect.width + space + if imageRect.height > titleRect.height { + height = imageRect.height + } else { + height = titleRect.height + } + + case .up: + if imageRect.width > titleRect.width { + width = imageRect.width + } else { + width = titleRect.width + } + height = titleRect.height + imageRect.height + space + + case .down: + if imageRect.width > titleRect.width { + width = imageRect.width + } else { + width = titleRect.width + } + height = titleRect.height + imageRect.height + space + + default: + width = imageRect.width + titleRect.width + space + if imageRect.height > titleRect.height { + height = imageRect.height + } else { + height = titleRect.height + } + } + + let size = CGSize(width: width + leftAndRightMargin * 2, height: height) + return size + } + + override func imageRect(forContentRect contentRect: CGRect) -> CGRect { + let imageSize = currentImage?.size ?? .zero + let textSize = self._textSize() + let contentWidth = self.frame.size.width + let contentHeight = self.frame.size.height + + var x: CGFloat = 0 + var y: CGFloat = 0 + var width: CGFloat = 0 + var height: CGFloat = 0 + + switch titleDirection { + case .left: + x = (contentWidth - space) / 2 - imageSize.width / 2 + textSize.width / 2 + space + y = contentHeight / 2 - imageSize.height / 2 + width = imageSize.width + height = imageSize.height + + case .up: + x = contentWidth / 2 - imageSize.width / 2 + y = (contentHeight - space) / 2 - imageSize.height / 2 + textSize.height / 2 + space + width = imageSize.width + height = imageSize.height + + case .down: + x = contentWidth / 2 - imageSize.width / 2 + y = (contentHeight - space) / 2 - imageSize.height / 2 - textSize.height / 2 + width = imageSize.width + height = imageSize.height + + default: + x = (contentWidth - space) / 2 - imageSize.width / 2 - textSize.width / 2 + y = contentHeight / 2 - imageSize.height / 2 + width = imageSize.width + height = imageSize.height + } + self.imageRect = CGRect(x: x, y: y, width: width, height: height) + + return self.imageRect + } + + + override func titleRect(forContentRect contentRect: CGRect) -> CGRect { + let imageSize = currentImage?.size ?? .zero + let textSize = self._textSize() + let contentWidth = self.frame.size.width + let contentHeight = self.frame.size.height + + var x: CGFloat = 0 + var y: CGFloat = 0 + var width: CGFloat = 0 + var height: CGFloat = 0 + + switch titleDirection { + case .left: + x = (contentWidth - space) / 2 - imageSize.width / 2 - textSize.width / 2 + y = contentHeight / 2 - textSize.height / 2 + width = textSize.width + height = textSize.height + + case .up: + x = contentWidth / 2 - textSize.width / 2 + y = (contentHeight - space) / 2 - textSize.height / 2 - imageSize.height / 2 + width = textSize.width + height = textSize.height + + case .down: + x = contentWidth / 2 - textSize.width / 2 + y = (contentHeight - space) / 2 - textSize.height / 2 + imageSize.height / 2 + space + width = textSize.width + height = textSize.height + + default: + x = (contentWidth - space) / 2 + imageSize.width / 2 - textSize.width / 2 + space + y = contentHeight / 2 - textSize.height / 2 + width = textSize.width + height = textSize.height + } + + self.titleRect = CGRect(x: x, y: y, width: width, height: height) + return self.titleRect + } + + + private var borderColors: [UInt : UIColor] = [:] + + func jx_setBorderColor(_ color: UIColor?, for state: UIControl.State) { + var arr = self.borderColors + let index = state.rawValue + arr[index] = color + if state == .selected { + let i = index + UIControl.State.highlighted.rawValue + arr[i] = color + } + self.borderColors = arr + updateStatus() + } + + func updateStatus() { + var color = self.borderColors[self.state.rawValue]?.cgColor + if (color == nil) { + color = self.borderColors[UIControl.State.normal.rawValue]?.cgColor + } + self.layer.borderColor = color + } + + override var isSelected: Bool { + didSet { + updateStatus() + } + } + + override var isEnabled: Bool { + didSet { + updateStatus() + } + } +} + + +extension JXButton { + + private func _textSize() -> CGSize { +// let size = CGSize(width: self.bounds.size.width, height: self.bounds.size.height) + + var attr: [NSAttributedString.Key : Any] = [:] + attr[NSAttributedString.Key.font] = jx_font + + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.lineBreakMode = .byTruncatingMiddle + attr[NSAttributedString.Key.paragraphStyle] = paragraphStyle + +// var rect = self.currentTitle?.ocString.boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attr, context: nil) ?? .zero + + if let font = jx_font { + var size = self.currentTitle?.size(font: font) ?? .zero + + if maxTitleWidth != 0 && maxTitleWidth < size.width { + size = CGSize(width: maxTitleWidth, height: size.height) + } + // if maxTitleWidth != 0 && maxTitleWidth < rect.size.width { + // rect.size = CGSize(width: maxTitleWidth, height: rect.size.height) + // } + return size + } else { + return .zero + } + } + +} diff --git a/Veloria/Thirdparty/JXUUID/JXUUID.h b/Veloria/Thirdparty/JXUUID/JXUUID.h new file mode 100644 index 0000000..242e0b6 --- /dev/null +++ b/Veloria/Thirdparty/JXUUID/JXUUID.h @@ -0,0 +1,20 @@ +// +// JXUUID.h +// 设备标识符 +// +// Created by 曾觉新 on 2017/8/24. +// Copyright © 2017年 曾觉新. All rights reserved. +// + +#import + +@interface JXUUID : NSObject + ++ (nonnull NSString *)uuid; ++ (nonnull NSString *)idfa; +/** + 重新安装app后,会发生变化 + */ ++ (nonnull NSString *)systemUUID; + +@end diff --git a/Veloria/Thirdparty/JXUUID/JXUUID.m b/Veloria/Thirdparty/JXUUID/JXUUID.m new file mode 100644 index 0000000..be414ea --- /dev/null +++ b/Veloria/Thirdparty/JXUUID/JXUUID.m @@ -0,0 +1,47 @@ +// +// JXUUID.m +// 设备标识符 +// +// Created by 曾觉新 on 2017/8/24. +// Copyright © 2017年 曾觉新. All rights reserved. +// + +#import "JXUUID.h" +#import +#import "PDKeyChain.h" +#import + +static NSString *const uuidKey = @"com.JXUUID"; + +@implementation JXUUID + ++ (nonnull NSString *)uuid +{ + static NSString *uuid; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + uuid = [PDKeyChain objectForKey:uuidKey]; + if (uuid && uuid.length > 0) { + } else { + uuid = [[NSUUID UUID] UUIDString]; + [PDKeyChain setObject:uuid forKey:uuidKey]; + } + }); + return uuid; +} ++ (nonnull NSString *)idfa +{ + static NSString *idfa; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; + }); + return idfa; +} + ++ (nonnull NSString *)systemUUID +{ + return [UIDevice currentDevice].identifierForVendor.UUIDString; +} + +@end diff --git a/Veloria/Thirdparty/JXUUID/PDKeyChain.h b/Veloria/Thirdparty/JXUUID/PDKeyChain.h new file mode 100755 index 0000000..45c8eb1 --- /dev/null +++ b/Veloria/Thirdparty/JXUUID/PDKeyChain.h @@ -0,0 +1,31 @@ +// +// PDKeyChain.h +// PDKeyChain +// +// Created by Panda on 16/8/23. +// Copyright © 2016年 v2panda. All rights reserved. +// + +#import +#import + +@interface PDKeyChain : NSObject +/** + * 从 KeyChain 中读取存储的数据 + * + * @return NSDictionary + */ ++ (NSDictionary *)getKeyChainData; + ++ (id)objectForKey:(NSString *)key; ++ (void)setObject:(id)object forKey:(NSString *)key; ++ (void)removeObjectForKey:(NSString *)key; ++ (void)removeAllObjects; + + +/** + * 删除 KeyChain 信息 + */ ++ (void)keyChainDelete; + +@end diff --git a/Veloria/Thirdparty/JXUUID/PDKeyChain.m b/Veloria/Thirdparty/JXUUID/PDKeyChain.m new file mode 100755 index 0000000..07ad864 --- /dev/null +++ b/Veloria/Thirdparty/JXUUID/PDKeyChain.m @@ -0,0 +1,100 @@ +// +// PDKeyChain.m +// PDKeyChain +// +// Created by Panda on 16/8/23. +// Copyright © 2016年 v2panda. All rights reserved. +// + +#import "PDKeyChain.h" + +static NSString * const kPDKeyChainKey = @"com.veloria.keychainKey"; + +@implementation PDKeyChain + ++ (void)keyChainDelete{ + [self delete:kPDKeyChainKey]; +} + ++ (NSDictionary *)getKeyChainData +{ + NSDictionary *dic = [self load:kPDKeyChainKey]; + if (!dic) { + dic = [NSDictionary dictionary]; + } + return dic; +} + ++ (void)setObject:(id)object forKey:(NSString *)key +{ + NSMutableDictionary *tempDic = [[self getKeyChainData] mutableCopy]; + [tempDic setObject:object forKey:key]; + [self save:kPDKeyChainKey data:tempDic]; +} ++ (id)objectForKey:(NSString *)key +{ + NSDictionary *tempDic = [self getKeyChainData]; + return tempDic[key]; +} ++ (void)removeObjectForKey:(NSString *)key +{ + NSMutableDictionary *tempDic = [[self getKeyChainData] mutableCopy]; + [tempDic removeObjectForKey:key]; + [self save:kPDKeyChainKey data:tempDic]; +} ++ (void)removeAllObjects +{ + NSMutableDictionary *tempDic = [[self getKeyChainData] mutableCopy]; + [tempDic removeAllObjects]; + [self save:kPDKeyChainKey data:tempDic]; +} + + + ++ (NSMutableDictionary *)getKeychainQuery:(NSString *)service { + return [NSMutableDictionary dictionaryWithObjectsAndKeys: + (id)kSecClassGenericPassword,(id)kSecClass, + service, (id)kSecAttrService, + service, (id)kSecAttrAccount, + (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible, + nil]; +} + ++ (void)save:(NSString *)service data:(id)data { + //Get search dictionary + NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; + //Delete old item before add new item + SecItemDelete((CFDictionaryRef)keychainQuery); + //Add new object to search dictionary(Attention:the data format) + [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData]; + //Add item to keychain with the search dictionary + SecItemAdd((CFDictionaryRef)keychainQuery, NULL); +} + ++ (id)load:(NSString *)service { + id ret = nil; + NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; + //Configure the search setting + //Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue + [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; + [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; + CFDataRef keyData = NULL; + if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) { + @try { + ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData]; + } @catch (NSException *e) { + NSLog(@"Unarchive of %@ failed: %@", service, e); + } @finally { + } + } + if (keyData) + CFRelease(keyData); + return ret; +} + ++ (void)delete:(NSString *)service { + NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; + SecItemDelete((CFDictionaryRef)keychainQuery); +} + +@end diff --git a/Veloria/Thirdparty/ZKCycleScrollView-Swift/ZKCycleScrollView.swift b/Veloria/Thirdparty/ZKCycleScrollView-Swift/ZKCycleScrollView.swift new file mode 100644 index 0000000..88639c6 --- /dev/null +++ b/Veloria/Thirdparty/ZKCycleScrollView-Swift/ZKCycleScrollView.swift @@ -0,0 +1,591 @@ +// +// ZKCycleScrollView.swift +// ZKCycleScrollViewDemo +// +// Created by bestdew on 2019/3/8. +// Copyright © 2019 bestdew. All rights reserved. +// +// d*##$. +// zP"""""$e. $" $o +//4$ '$ $" $ +//'$ '$ J$ $F +// 'b $k $> $ +// $k $r J$ d$ +// '$ $ $" $~ +// '$ "$ '$E $ +// $ $L $" $F ... +// $. 4B $ $$$*"""*b +// '$ $. $$ $$ $F +// "$ R$ $F $" $ +// $k ?$ u* dF .$ +// ^$. $$" z$ u$$$$e +// #$b $E.dW@e$" ?$ +// #$ .o$$# d$$$$c ?F +// $ .d$$#" . zo$> #$r .uF +// $L .u$*" $&$$$k .$$d$$F +// $$" ""^"$$$P"$P9$ +// JP .o$$$$u:$P $$ +// $ ..ue$" "" $" +// d$ $F $ +// $$ ....udE 4B +// #$ """"` $r @$ +// ^$L '$ $F +// RN 4N $ +// *$b d$ +// $$k $F +// $$b $F +// $"" $F +// '$ $ +// $L $ +// '$ $ +// $ $ + +import UIKit + +public typealias ZKCycleScrollViewCell = UICollectionViewCell + +public enum ZKScrollDirection: Int { + case horizontal + case vertical +} + +@objc public protocol ZKCycleScrollViewDataSource: NSObjectProtocol { + /// Return number of pages + func numberOfItems(in cycleScrollView: ZKCycleScrollView) -> Int + /// The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndex: + func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, cellForItemAt index: Int) -> ZKCycleScrollViewCell +} + +@objc public protocol ZKCycleScrollViewDelegate: NSObjectProtocol { + /// Called when the cell is clicked + @objc optional func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, didSelectItemAt index: Int) + /// Called when the offset changes. The progress range is from 0 to the maximum index value, which means the progress value for a round of scrolling + @objc optional func cycleScrollViewDidScroll(_ cycleScrollView: ZKCycleScrollView, progress: Double) + /// Called when scrolling to a new index page + @objc optional func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, didScrollFromIndex fromIndex: Int, toIndex: Int) +} + +@IBDesignable open class ZKCycleScrollView: UIView { + + @IBOutlet open weak var delegate: ZKCycleScrollViewDelegate? + @IBOutlet open weak var dataSource: ZKCycleScrollViewDataSource? + + + var currentIdx = 0 + var stepLength: CGFloat? // 自定义每个时间间隔移动的步长 + + #if TARGET_INTERFACE_BUILDER + @IBInspectable open var scrollDirection: Int = 0 + #else + /// default horizontal. scroll direction + open var scrollDirection: ZKScrollDirection = .horizontal { + didSet { + switch scrollDirection { + case .vertical: + flowLayout?.scrollDirection = .vertical + default: + flowLayout?.scrollDirection = .horizontal + } + } + } + #endif + /// default 3.f. automatic scroll time interval + @IBInspectable open var autoScrollInterval: TimeInterval = 3 { + didSet { + addTimer() + } + } + @IBInspectable open var isAutoScroll: Bool = true { + didSet { + addTimer() + } + } + /// default true. turn off any dragging temporarily + @IBInspectable open var allowsDragging: Bool = true { + didSet { + collectionView.isScrollEnabled = allowsDragging + } + } + /// default the view size + @IBInspectable open var itemSize: CGSize = CGSize.zero { + didSet { + itemSizeFlag = true + flowLayout.itemSize = itemSize + flowLayout.headerReferenceSize = CGSize(width: (bounds.width - itemSize.width) / 2, height: (bounds.height - itemSize.height) / 2) + flowLayout.footerReferenceSize = CGSize(width: (bounds.width - itemSize.width) / 2, height: (bounds.height - itemSize.height) / 2) + } + } + /// default 0.0 + @IBInspectable open var itemSpacing: CGFloat = 0.0 { + didSet { + flowLayout.minimumLineSpacing = itemSpacing + } + } + /// default 1.f(no scaling), it ranges from 0.f to 1.f + @IBInspectable open var itemZoomScale: CGFloat = 1.0 { + didSet { + flowLayout.zoomScale = itemZoomScale + } + } + + ///item渐隐效果 + open var itemAlpha = false { + didSet { + flowLayout.itemAlpha = itemAlpha + } + } + + ///旋转角度 + open var rotationAngle = 0.0 { + didSet { + flowLayout.rotationAngle = rotationAngle + } + } + + @IBInspectable open var hidesPageControl: Bool = false { + didSet { + pageControl?.isHidden = hidesPageControl + } + } + @IBInspectable open var pageIndicatorTintColor: UIColor = UIColor.gray { + didSet { + pageControl?.pageIndicatorTintColor = pageIndicatorTintColor + } + } + @IBInspectable open var currentPageIndicatorTintColor: UIColor = UIColor.white { + didSet { + pageControl?.currentPageIndicatorTintColor = currentPageIndicatorTintColor + } + } + /// current page index + open var pageIndex: Int { + return changeIndex(currentIndex()) + } + /// current content offset + open var contentOffset: CGPoint { + let num = CGFloat(numberOfAddedCells() / 2) + switch scrollDirection { + case .vertical: + return CGPoint(x: 0.0, y: max(0.0, collectionView.contentOffset.y - (flowLayout.itemSize.height + flowLayout.minimumLineSpacing) * num)) + default: + return CGPoint(x: max(0.0, collectionView.contentOffset.x - (flowLayout.itemSize.width + flowLayout.minimumLineSpacing) * num), y: 0.0) + } + } + /// infinite cycle + @IBInspectable open private(set) var isInfiniteLoop: Bool = true + /// load completed callback + open var loadCompletion: (() -> Void)? = nil + open var pageControlFrame: CGRect? + var pageControl: UIPageControl! + var collectionView: UICollectionView! + private var flowLayout: ZKCycleScrollViewFlowLayout! + private var timer: Timer? + private var numberOfItems: Int = 0 + private var fromIndex: Int = 0 + private var itemSizeFlag: Bool = false + private var indexOffset: Int = 0 + private var configuredFlag: Bool = false + private var tempIndex: Int = 0 + + // MARK: - Open Func + open func register(_ cellClass: AnyClass?, forCellWithReuseIdentifier identifier: String) { + collectionView.register(cellClass, forCellWithReuseIdentifier: identifier) + } + + open func register(_ nib: UINib?, forCellWithReuseIdentifier identifier: String) { + collectionView.register(nib, forCellWithReuseIdentifier: identifier) + } + + open func dequeueReusableCell(withReuseIdentifier identifier: String, for index: Int) -> ZKCycleScrollViewCell { + let indexPath = IndexPath(item: changeIndex(index), section: 0) + return collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) + } + + open func reloadData() { + removeTimer() + UIView.performWithoutAnimation { + self.collectionView.reloadData() + } + collectionView.performBatchUpdates(nil) { _ in + self.configuration() + self.loadCompletion?() + } + } + + /// Call -beginUpdates and -endUpdates to update layout + /// Allows multiple scrollDirection/itemSize/itemSpacing/itemZoomScale to be set simultaneously. + open func beginUpdates() { + tempIndex = pageIndex + removeTimer() + } + + open func endUpdates() { + flowLayout.invalidateLayout() + scrollToItem(at: tempIndex, animated: false) + addTimer() + } + + /// Scroll to page + open func scrollToItem(at index: Int, animated: Bool) { + let num = numberOfAddedCells() + guard index >= 0 && index <= numberOfItems - 1 - num else { + print("⚠️attempt to scroll to invalid index:\(index)") + return + } + removeTimer() + let idx = index + num / 2 + let position = scrollPosition() + let indexPath = IndexPath(item: idx, section: 0) + collectionView.scrollToItem(at: indexPath, at: position, animated: animated) + + addTimer() + } + + /// Returns the visible cell object at the specified index + open func cellForItem(at index: Int) -> ZKCycleScrollViewCell? { + let num = numberOfAddedCells() + guard index >= 0 && index <= numberOfItems - 1 - num else { + return nil + } + let idx = index + num / 2 + let indexPath = IndexPath(item: idx, section: 0) + let cell = collectionView.cellForItem(at: indexPath) + return cell + } + + // MARK: - Init + public init(frame: CGRect, shouldInfiniteLoop infiniteLoop: Bool? = nil) { + super.init(frame: frame) + + isInfiniteLoop = infiniteLoop ?? true + initialization() + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + initialization() + } + + override open func layoutSubviews() { + super.layoutSubviews() + + if itemSizeFlag { + flowLayout.itemSize = itemSize + flowLayout.headerReferenceSize = CGSize(width: (bounds.width - itemSize.width) / 2, height: (bounds.height - itemSize.height) / 2) + flowLayout.footerReferenceSize = CGSize(width: (bounds.width - itemSize.width) / 2, height: (bounds.height - itemSize.height) / 2) + } else { + flowLayout.itemSize = bounds.size + flowLayout.headerReferenceSize = CGSize.zero + flowLayout.footerReferenceSize = CGSize.zero + } + collectionView.frame = bounds + if let pageControlFrame = pageControlFrame { + pageControl.frame = pageControlFrame + } else { + pageControl.frame = CGRect(x: 0.0, y: bounds.height - 15.0, width: bounds.width, height: 15.0) + } + } + + override open func willMove(toSuperview newSuperview: UIView?) { + if newSuperview == nil { removeTimer() } + } + + override open func setValue(_ value: Any?, forUndefinedKey key: String) { + guard key == "scrollDirection" else { + return; + } + let direction = value as! Int + if direction == 1 { + scrollDirection = .vertical + } else { + scrollDirection = .horizontal + } + } + + deinit { + collectionView.delegate = nil + collectionView.dataSource = nil + } + + // MARK: - Private Func + private func initialization() { + flowLayout = ZKCycleScrollViewFlowLayout() + flowLayout.minimumLineSpacing = 0 + flowLayout.minimumInteritemSpacing = 0 + flowLayout.scrollDirection = .horizontal + + collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: flowLayout) + collectionView.backgroundColor = nil + collectionView.delegate = self + collectionView.dataSource = self + collectionView.scrollsToTop = false + collectionView.bounces = false + collectionView.showsVerticalScrollIndicator = false + collectionView.showsHorizontalScrollIndicator = false + addSubview(collectionView) + + pageControl = UIPageControl() + pageControl.isEnabled = false + pageControl.hidesForSinglePage = true + pageControl.pageIndicatorTintColor = UIColor.gray + pageControl.currentPageIndicatorTintColor = UIColor.white + addSubview(pageControl); + + DispatchQueue.main.async { + self.configuration() + self.loadCompletion?() + } + } + + private func configuration() { + fromIndex = 0 + indexOffset = 0 + configuredFlag = false + + guard numberOfItems > 1 else { return } + + let position = scrollPosition() + if isInfiniteLoop { + let indexPath = IndexPath(item: 2, section: 0) + collectionView.scrollToItem(at: indexPath, at: position, animated: false) + } + + addTimer() + updatePageControl() + + configuredFlag = true + } + + func addTimer() { + removeTimer() + + if numberOfItems < 2 || !isAutoScroll || autoScrollInterval <= 0.0 { return } + timer = Timer.scheduledTimer(timeInterval: autoScrollInterval, target: YYWeakProxy(target: self), selector: #selector(automaticScroll), userInfo: nil, repeats: true) + RunLoop.main.add(timer!, forMode: .common) + } + + private func updatePageControl() { + let num = numberOfAddedCells() + pageControl.currentPage = 0 + pageControl.numberOfPages = max(0, numberOfItems - num) + pageControl.isHidden = (hidesPageControl || pageControl.numberOfPages < 2) + } + + private func numberOfAddedCells() -> Int { + return isInfiniteLoop ? 4 : 0 + } + + @objc private func automaticScroll() { + var index = currentIndex() + 1 + if !isInfiniteLoop && index >= numberOfItems { + index = 0 + } + + if let stepLength = stepLength { + let offsetX = self.collectionView.contentOffset.x + let width = flowLayout.itemSize.width + flowLayout.minimumLineSpacing + self.collectionView.contentOffset = CGPoint(x: offsetX+stepLength, y: self.contentOffset.y) + + let position = scrollPosition() + if self.currentIndex() == 1 { + let indexPath = IndexPath(item: numberOfItems - 3, section: 0) + collectionView.scrollToItem(at: indexPath, at: position, animated: false) + } else if self.currentIndex() == numberOfItems - 2 { + let offsetx = (width)*CGFloat(2) - width*0.5 + self.collectionView.contentOffset = CGPoint(x: offsetx, y: self.contentOffset.y) + self.collectionView.reloadData() + } + + } else { + let position = scrollPosition() + let indexPath = IndexPath(item: index, section: 0) + collectionView.scrollToItem(at: indexPath, at: position, animated: true) + } + + } + + func removeTimer() { + timer?.invalidate() + timer = nil + } + + private func scrollPosition() -> UICollectionView.ScrollPosition { + switch scrollDirection { + case .vertical: + return .centeredVertically + default: + return .centeredHorizontally + } + } + + private func currentIndex() -> Int { + guard numberOfItems > 0 else { + return -1 + } + + var index = 0 + var minimumIndex = 0 + var maximumIndex = numberOfItems - 1 + + if numberOfItems == 1 { + return index + } + + if isInfiniteLoop { + minimumIndex = 1 + maximumIndex = numberOfItems - 2 + } + + switch scrollDirection { + case .vertical: + let height = flowLayout.itemSize.height + flowLayout.minimumLineSpacing + index = Int((collectionView.contentOffset.y + height / 2) / height) + default: + let width = flowLayout.itemSize.width + flowLayout.minimumLineSpacing + index = Int((collectionView.contentOffset.x + width / 2) / width) + } + return min(maximumIndex, max(minimumIndex, index)) + } + + private func changeIndex(_ index: Int) -> Int { + guard isInfiniteLoop && numberOfItems > 1 else { + return index + } + + var idx = index + + if index == 0 { + idx = numberOfItems - 6 + } else if index == 1 { + idx = numberOfItems - 5 + } else if index == numberOfItems - 2 { + idx = 0 + } else if index == numberOfItems - 1 { + idx = 1 + } else { + idx = index - 2 + } + return idx + } +} + +extension ZKCycleScrollView: UICollectionViewDelegate { + + public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { + return true + } + + public func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) { + if let delegate = delegate, delegate.responds(to: #selector(ZKCycleScrollViewDelegate.cycleScrollView(_:didSelectItemAt:))) { + removeTimer() + } + } + + public func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) { + if let delegate = delegate, delegate.responds(to: #selector(ZKCycleScrollViewDelegate.cycleScrollView(_:didSelectItemAt:))) { + addTimer() + } + } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if let delegate = delegate, delegate.responds(to: #selector(ZKCycleScrollViewDelegate.cycleScrollView(_:didSelectItemAt:))) { + delegate.cycleScrollView!(self, didSelectItemAt: changeIndex(indexPath.item)) + } + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + pageControl.currentPage = pageIndex + if let delegate = delegate, delegate.responds(to: #selector(ZKCycleScrollViewDelegate.cycleScrollViewDidScroll(_:progress:))) { + var total: CGFloat = 0.0 + var offset: CGFloat = 0.0 + let num = numberOfAddedCells() + + switch scrollDirection { + case .vertical: + total = CGFloat(numberOfItems - 1 - num) * (flowLayout.itemSize.height + flowLayout.minimumLineSpacing) + offset = contentOffset.y.truncatingRemainder(dividingBy:((flowLayout.itemSize.height + flowLayout.minimumLineSpacing) * CGFloat(numberOfItems - num))) + default: + total = CGFloat(numberOfItems - 1 - num) * (flowLayout.itemSize.width + flowLayout.minimumLineSpacing) + offset = contentOffset.x.truncatingRemainder(dividingBy:((flowLayout.itemSize.width + flowLayout.minimumLineSpacing) * CGFloat(numberOfItems - num))) + } + let percent = Double(offset / total) + let progress = percent * Double(numberOfItems - 1 - num) + delegate.cycleScrollViewDidScroll!(self, progress: progress) + } + } + + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + removeTimer() + } + + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if let _ = stepLength { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.addTimer() + } + } else { + addTimer() + } + } + + public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + let index = currentIndex() + + if isInfiniteLoop { + let position = scrollPosition() + if index == 1 { + let indexPath = IndexPath(item: numberOfItems - 3, section: 0) + collectionView.scrollToItem(at: indexPath, at: position, animated: false) + } else if index == numberOfItems - 2 { + let indexPath = IndexPath(item: 2, section: 0) + collectionView.scrollToItem(at: indexPath, at: position, animated: false) + } + } + + let toIndex = changeIndex(index) + if let delegate = delegate, delegate.responds(to: #selector(ZKCycleScrollViewDelegate.cycleScrollView(_:didScrollFromIndex:toIndex:))) { + delegate.cycleScrollView!(self, didScrollFromIndex: fromIndex, toIndex: toIndex) + } + fromIndex = toIndex + } + + public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + guard pageIndex == fromIndex else { return } + + let sum = velocity.x + velocity.y + if sum > 0 { + indexOffset = 1 + } else if sum < 0 { + indexOffset = -1 + } else { + indexOffset = 0 + } + } + + public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) { + let position = scrollPosition() + var index = currentIndex() + indexOffset + index = max(0, index) + index = min(numberOfItems - 1, index) + let indexPath = IndexPath(item: index, section: 0) + collectionView.scrollToItem(at: indexPath, at: position, animated: true) + indexOffset = 0 + } +} + +extension ZKCycleScrollView: UICollectionViewDataSource { + + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + numberOfItems = dataSource?.numberOfItems(in: self) ?? 0 + if isInfiniteLoop && numberOfItems > 1 { + numberOfItems += numberOfAddedCells() + } + return numberOfItems + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let index = changeIndex(indexPath.item) + return (dataSource?.cycleScrollView(self, cellForItemAt: index))! + } +} diff --git a/Veloria/Thirdparty/ZKCycleScrollView-Swift/ZKCycleScrollViewFlowLayout.swift b/Veloria/Thirdparty/ZKCycleScrollView-Swift/ZKCycleScrollViewFlowLayout.swift new file mode 100644 index 0000000..7f8d74f --- /dev/null +++ b/Veloria/Thirdparty/ZKCycleScrollView-Swift/ZKCycleScrollViewFlowLayout.swift @@ -0,0 +1,168 @@ +// +// ZKCycleScrollViewFlowLayout.swift +// ZKCycleScrollViewDemo +// +// Created by bestdew on 2019/3/22. +// Copyright © 2019 bestdew. All rights reserved. +// +// d*##$. +// zP"""""$e. $" $o +//4$ '$ $" $ +//'$ '$ J$ $F +// 'b $k $> $ +// $k $r J$ d$ +// '$ $ $" $~ +// '$ "$ '$E $ +// $ $L $" $F ... +// $. 4B $ $$$*"""*b +// '$ $. $$ $$ $F +// "$ R$ $F $" $ +// $k ?$ u* dF .$ +// ^$. $$" z$ u$$$$e +// #$b $E.dW@e$" ?$ +// #$ .o$$# d$$$$c ?F +// $ .d$$#" . zo$> #$r .uF +// $L .u$*" $&$$$k .$$d$$F +// $$" ""^"$$$P"$P9$ +// JP .o$$$$u:$P $$ +// $ ..ue$" "" $" +// d$ $F $ +// $$ ....udE 4B +// #$ """"` $r @$ +// ^$L '$ $F +// RN 4N $ +// *$b d$ +// $$k $F +// $$b $F +// $"" $F +// '$ $ +// $L $ +// '$ $ +// $ $ + +import UIKit + +open class ZKCycleScrollViewFlowLayout: UICollectionViewFlowLayout { + + open var zoomScale: CGFloat = 1.0 + ///item渐隐效果 + open var itemAlpha = false + ///旋转角度 + open var rotationAngle = 0.0 + + override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + return true + } + + override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + let attributes: [UICollectionViewLayoutAttributes] = NSArray(array: super.layoutAttributesForElements(in: rect) ?? [], copyItems: true) as! [UICollectionViewLayoutAttributes] + if let collectionView = collectionView { + switch scrollDirection { + case .vertical: + let offset = collectionView.bounds.midY; + let distanceForScale = itemSize.height + minimumLineSpacing; + + for attr in attributes { + var scale: CGFloat = 0.0; + let distance = abs(offset - attr.center.y) + if distance >= distanceForScale { + scale = zoomScale; + } else if distance == 0.0 { + scale = 1.0 + attr.zIndex = 1 + } else { + scale = zoomScale + (distanceForScale - distance) * (1.0 - zoomScale) / distanceForScale + } + attr.transform = CGAffineTransform(scaleX: scale, y: scale) + } + default: + let offset = collectionView.bounds.midX; + let distanceForScale = itemSize.width + minimumLineSpacing; + + + for attr in attributes { + var scale: CGFloat = 0.0; + let distance = offset - attr.center.x + let abs_distance = abs(distance) + let screenScale = distance / (collectionView.bounds.width) + + if abs_distance >= distanceForScale { + scale = zoomScale; + } else if abs_distance == 0.0 { + scale = 1.0 + attr.zIndex = 1 + } else { + scale = zoomScale + (distanceForScale - abs_distance) * (1.0 - zoomScale) / distanceForScale + } + + if itemAlpha { + var alpha = 1 - abs(screenScale) + 0.1 + if alpha < 0 { + alpha = 0 + } else if alpha > 1 { + alpha = 1 + } + attr.alpha = alpha + } else { + attr.alpha = 1 + } + //缩放 + let scaleTransform = CGAffineTransform(scaleX: scale, y: scale) + + //旋转 +// let rotationAngle = kSPAngleToRadians(angle: -screenScale * self.rotationAngle) +// let rotationTransform = CGAffineTransform(rotationAngle: rotationAngle) +// +// // 组合旋转和缩放变换 +// let combinedTransform = rotationTransform.concatenating(scaleTransform) + // 应用变换 + attr.transform = scaleTransform + } + } + } + return attributes + } + + override open func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { + var point = proposedContentOffset + if let collectionView = collectionView { + switch scrollDirection { + case .vertical: + /// 计算出最终显示的矩形框 + let rect = CGRect(x: 0.0, y: point.y, width: collectionView.bounds.width, height: collectionView.bounds.height) + /// 计算collectionView最中心点的y值 + let centerY = point.y + collectionView.bounds.size.height * 0.5 + /// 存放最小的间距值 + var minDelta = CGFloat(MAXFLOAT) + /// 获得super已经计算好的布局属性 + if let attributes = super.layoutAttributesForElements(in: rect) { + for attr in attributes { + if abs(minDelta) > abs(attr.center.y - centerY) { + minDelta = attr.center.y - centerY + } + } + } + /// 修改原有的偏移量 + point.y += minDelta + default: + /// 计算出最终显示的矩形框 + let rect = CGRect(x: point.x, y: 0.0, width: collectionView.bounds.width, height: collectionView.bounds.height) + /// 计算collectionView最中心点的y值 + let centerX = point.x + collectionView.bounds.size.width * 0.5 + /// 存放最小的间距值 + var minDelta = CGFloat(MAXFLOAT) + /// 获得super已经计算好的布局属性 + if let attributes = super.layoutAttributesForElements(in: rect) { + for attr in attributes { + if abs(minDelta) > abs(attr.center.x - centerX) { + minDelta = attr.center.x - centerX + } + } + } + /// 修改原有的偏移量 + point.x += minDelta + } + } + return point + } +}