diff --git a/Veloria.xcodeproj/project.pbxproj b/Veloria.xcodeproj/project.pbxproj index 99b3cef..7c241f6 100644 --- a/Veloria.xcodeproj/project.pbxproj +++ b/Veloria.xcodeproj/project.pbxproj @@ -97,6 +97,21 @@ BF0FA76F2DE062A700C9E5F2 /* VPRateSelectedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA76E2DE062A700C9E5F2 /* VPRateSelectedView.swift */; }; BF0FA7712DE062EB00C9E5F2 /* VPVideoRateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7702DE062EB00C9E5F2 /* VPVideoRateModel.swift */; }; BF0FA7732DE0671200C9E5F2 /* VPRateSelectedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7722DE0671200C9E5F2 /* VPRateSelectedCell.swift */; }; + BF0FA7752DE071B500C9E5F2 /* VPSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7742DE071B500C9E5F2 /* VPSearchViewController.swift */; }; + BF0FA7772DE0735800C9E5F2 /* VPSearchInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7762DE0735800C9E5F2 /* VPSearchInputView.swift */; }; + BF0FA7792DE075FF00C9E5F2 /* VPSearchHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7782DE075FF00C9E5F2 /* VPSearchHomeView.swift */; }; + BF0FA77B2DE0788A00C9E5F2 /* UIStackView+VPAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA77A2DE0788A00C9E5F2 /* UIStackView+VPAdd.swift */; }; + BF0FA77D2DE078D400C9E5F2 /* VPSearchRecommendedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA77C2DE078D400C9E5F2 /* VPSearchRecommendedView.swift */; }; + BF0FA7812DE150F700C9E5F2 /* VPSearchRecommendedContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7802DE150F700C9E5F2 /* VPSearchRecommendedContentView.swift */; }; + BF0FA7832DE1533E00C9E5F2 /* VPGradientLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7822DE1533E00C9E5F2 /* VPGradientLabel.swift */; }; + BF0FA7852DE1561D00C9E5F2 /* VPSearchRecommendedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7842DE1561D00C9E5F2 /* VPSearchRecommendedCell.swift */; }; + BF0FA7872DE1601200C9E5F2 /* VPTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7862DE1601200C9E5F2 /* VPTextField.swift */; }; + BF0FA7892DE161F200C9E5F2 /* VPSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7882DE161F200C9E5F2 /* VPSearchResultView.swift */; }; + BF0FA78B2DE164C100C9E5F2 /* VPSearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA78A2DE164C100C9E5F2 /* VPSearchResultCell.swift */; }; + BF0FA78D2DE16A8B00C9E5F2 /* VPSearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA78C2DE16A8B00C9E5F2 /* VPSearchViewModel.swift */; }; + BF0FA78F2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA78E2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift */; }; + BF0FA7912DE16CBF00C9E5F2 /* VPSearchHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7902DE16CBF00C9E5F2 /* VPSearchHistoryView.swift */; }; + BF0FA7942DE16E9300C9E5F2 /* JXTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0FA7922DE16E9300C9E5F2 /* JXTagView.swift */; }; F939C04AD4003BA127F15C28 /* Pods_Veloria.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34F57E87E765BF8D72A43DCA /* Pods_Veloria.framework */; }; /* End PBXBuildFile section */ @@ -200,6 +215,21 @@ BF0FA76E2DE062A700C9E5F2 /* VPRateSelectedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPRateSelectedView.swift; sourceTree = ""; }; BF0FA7702DE062EB00C9E5F2 /* VPVideoRateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPVideoRateModel.swift; sourceTree = ""; }; BF0FA7722DE0671200C9E5F2 /* VPRateSelectedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPRateSelectedCell.swift; sourceTree = ""; }; + BF0FA7742DE071B500C9E5F2 /* VPSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchViewController.swift; sourceTree = ""; }; + BF0FA7762DE0735800C9E5F2 /* VPSearchInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchInputView.swift; sourceTree = ""; }; + BF0FA7782DE075FF00C9E5F2 /* VPSearchHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchHomeView.swift; sourceTree = ""; }; + BF0FA77A2DE0788A00C9E5F2 /* UIStackView+VPAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIStackView+VPAdd.swift"; sourceTree = ""; }; + BF0FA77C2DE078D400C9E5F2 /* VPSearchRecommendedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchRecommendedView.swift; sourceTree = ""; }; + BF0FA7802DE150F700C9E5F2 /* VPSearchRecommendedContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchRecommendedContentView.swift; sourceTree = ""; }; + BF0FA7822DE1533E00C9E5F2 /* VPGradientLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPGradientLabel.swift; sourceTree = ""; }; + BF0FA7842DE1561D00C9E5F2 /* VPSearchRecommendedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchRecommendedCell.swift; sourceTree = ""; }; + BF0FA7862DE1601200C9E5F2 /* VPTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPTextField.swift; sourceTree = ""; }; + BF0FA7882DE161F200C9E5F2 /* VPSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchResultView.swift; sourceTree = ""; }; + BF0FA78A2DE164C100C9E5F2 /* VPSearchResultCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchResultCell.swift; sourceTree = ""; }; + BF0FA78C2DE16A8B00C9E5F2 /* VPSearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchViewModel.swift; sourceTree = ""; }; + BF0FA78E2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPUserDefaultsKey.swift; sourceTree = ""; }; + BF0FA7902DE16CBF00C9E5F2 /* VPSearchHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPSearchHistoryView.swift; sourceTree = ""; }; + BF0FA7922DE16E9300C9E5F2 /* JXTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JXTagView.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 */ @@ -284,6 +314,7 @@ 1B056E362DDAC1E8007EE38D /* Class */ = { isa = PBXGroup; children = ( + BF0FA7952DE1948A00C9E5F2 /* MyList */, 1B056E522DDACBF4007EE38D /* Home */, BF0FA7422DDF024400C9E5F2 /* Explore */, BF0FA6FD2DDC65F300C9E5F2 /* Player */, @@ -324,6 +355,8 @@ BF0FA7252DDC8F7600C9E5F2 /* VPCollectionView.swift */, BF0FA7272DDC91F800C9E5F2 /* VPCollectionViewCell.swift */, BF0FA76C2DE053C100C9E5F2 /* VPScrollView.swift */, + BF0FA7822DE1533E00C9E5F2 /* VPGradientLabel.swift */, + BF0FA7862DE1601200C9E5F2 /* VPTextField.swift */, ); path = View; sourceTree = ""; @@ -350,6 +383,7 @@ isa = PBXGroup; children = ( 1B056E4A2DDAC6BA007EE38D /* VPDefine.swift */, + BF0FA78E2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift */, ); path = Define; sourceTree = ""; @@ -378,6 +412,7 @@ BF0FA7212DDC859D00C9E5F2 /* NSNumber+VPAdd.swift */, BF0FA72F2DDEBB1600C9E5F2 /* UIButton+VPAdd.swift */, BF0FA7402DDEFBC700C9E5F2 /* UIScrollView+VPRefresh.swift */, + BF0FA77A2DE0788A00C9E5F2 /* UIStackView+VPAdd.swift */, ); path = Extension; sourceTree = ""; @@ -406,6 +441,7 @@ children = ( 1B056E562DDACC6B007EE38D /* VPHomePageViewController.swift */, BF0FA7382DDECF8900C9E5F2 /* VPHomeListViewController.swift */, + BF0FA7742DE071B500C9E5F2 /* VPSearchViewController.swift */, ); path = Controller; sourceTree = ""; @@ -462,6 +498,7 @@ BF0FA6E82DDC5F6F00C9E5F2 /* Thirdparty */ = { isa = PBXGroup; children = ( + BF0FA7932DE16E9300C9E5F2 /* JXTagView */, BF0FA6ED2DDC5F8700C9E5F2 /* JXUUID */, BF0FA7152DDC78FF00C9E5F2 /* ZKCycleScrollView-Swift */, BF0FA71F2DDC83AE00C9E5F2 /* JXButton */, @@ -536,6 +573,7 @@ isa = PBXGroup; children = ( BF0FA7042DDC67AC00C9E5F2 /* VPHomeViewModel.swift */, + BF0FA78C2DE16A8B00C9E5F2 /* VPSearchViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -552,6 +590,14 @@ BF0FA72D2DDD7DD400C9E5F2 /* VPHomeRankingCell.swift */, BF0FA73A2DDED1C700C9E5F2 /* VPHomeListCell.swift */, BF0FA73E2DDEF26E00C9E5F2 /* VPHomeSearchButton.swift */, + BF0FA7762DE0735800C9E5F2 /* VPSearchInputView.swift */, + BF0FA7782DE075FF00C9E5F2 /* VPSearchHomeView.swift */, + BF0FA77C2DE078D400C9E5F2 /* VPSearchRecommendedView.swift */, + BF0FA7802DE150F700C9E5F2 /* VPSearchRecommendedContentView.swift */, + BF0FA7842DE1561D00C9E5F2 /* VPSearchRecommendedCell.swift */, + BF0FA7882DE161F200C9E5F2 /* VPSearchResultView.swift */, + BF0FA78A2DE164C100C9E5F2 /* VPSearchResultCell.swift */, + BF0FA7902DE16CBF00C9E5F2 /* VPSearchHistoryView.swift */, ); path = View; sourceTree = ""; @@ -657,6 +703,37 @@ path = Controller; sourceTree = ""; }; + BF0FA7932DE16E9300C9E5F2 /* JXTagView */ = { + isa = PBXGroup; + children = ( + BF0FA7922DE16E9300C9E5F2 /* JXTagView.swift */, + ); + path = JXTagView; + sourceTree = ""; + }; + BF0FA7952DE1948A00C9E5F2 /* MyList */ = { + isa = PBXGroup; + children = ( + BF0FA7972DE1949B00C9E5F2 /* View */, + BF0FA7962DE1949300C9E5F2 /* Controller */, + ); + path = MyList; + sourceTree = ""; + }; + BF0FA7962DE1949300C9E5F2 /* Controller */ = { + isa = PBXGroup; + children = ( + ); + path = Controller; + sourceTree = ""; + }; + BF0FA7972DE1949B00C9E5F2 /* View */ = { + isa = PBXGroup; + children = ( + ); + path = View; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -786,16 +863,20 @@ BF0FA7002DDC665300C9E5F2 /* VPShortModel.swift in Sources */, 1B056E7B2DDB37BA007EE38D /* VPGradientView.swift in Sources */, BF0FA7262DDC8F7600C9E5F2 /* VPCollectionView.swift in Sources */, + BF0FA78B2DE164C100C9E5F2 /* VPSearchResultCell.swift in Sources */, BF0FA71D2DDC807200C9E5F2 /* UIImageView+VPAdd.swift in Sources */, BF0FA6DC2DDC5CD700C9E5F2 /* VPTokenModel.swift in Sources */, BF0FA7572DDF159B00C9E5F2 /* VPExploreViewController.swift in Sources */, 1B056E772DDB3641007EE38D /* VPTabBarItemNormalVew.swift in Sources */, 1B056E4B2DDAC6BA007EE38D /* VPDefine.swift in Sources */, 1B056E702DDB019B007EE38D /* VPTabBarItemContainer.swift in Sources */, + BF0FA7752DE071B500C9E5F2 /* VPSearchViewController.swift in Sources */, + BF0FA78D2DE16A8B00C9E5F2 /* VPSearchViewModel.swift in Sources */, 1B056E592DDACD44007EE38D /* VPTabBarItemContentView.swift in Sources */, 1B056E2B2DDAC0FD007EE38D /* AppDelegate.swift in Sources */, 1B056E6C2DDADAA1007EE38D /* VPTabBar.swift in Sources */, BF0FA72E2DDD7DD400C9E5F2 /* VPHomeRankingCell.swift in Sources */, + BF0FA7792DE075FF00C9E5F2 /* VPSearchHomeView.swift in Sources */, BF0FA7302DDEBB1600C9E5F2 /* UIButton+VPAdd.swift in Sources */, BF0FA74C2DDF060200C9E5F2 /* VPVideoPlayerViewController.swift in Sources */, BF0FA76D2DE053C100C9E5F2 /* VPScrollView.swift in Sources */, @@ -809,12 +890,17 @@ BF0FA7592DDF1C2800C9E5F2 /* VPPlayerProgressView.swift in Sources */, BF0FA71B2DDC7FF200C9E5F2 /* VPImageView.swift in Sources */, BF0FA7522DDF134700C9E5F2 /* VPVideoPlayerControlView.swift in Sources */, + BF0FA7852DE1561D00C9E5F2 /* VPSearchRecommendedCell.swift in Sources */, + BF0FA7832DE1533E00C9E5F2 /* VPGradientLabel.swift in Sources */, 1B056E2C2DDAC0FD007EE38D /* SceneDelegate.swift in Sources */, BF0FA75D2DDF208400C9E5F2 /* VPExplorePlayerControlView.swift in Sources */, 1B056E462DDAC370007EE38D /* UIScreen+VPAdd.swift in Sources */, BF0FA7692DE0502900C9E5F2 /* VPEpisodeCell.swift in Sources */, BF0FA73D2DDED2D000C9E5F2 /* VPVideoAPI.swift in Sources */, BF0FA6EE2DDC5F8700C9E5F2 /* JXUUID.m in Sources */, + BF0FA77D2DE078D400C9E5F2 /* VPSearchRecommendedView.swift in Sources */, + BF0FA7812DE150F700C9E5F2 /* VPSearchRecommendedContentView.swift in Sources */, + BF0FA7872DE1601200C9E5F2 /* VPTextField.swift in Sources */, BF0FA7052DDC67AC00C9E5F2 /* VPHomeViewModel.swift in Sources */, BF0FA6EF2DDC5F8700C9E5F2 /* PDKeyChain.m in Sources */, BF0FA75F2DDFFDB000C9E5F2 /* VPDetailPlayerViewController.swift in Sources */, @@ -823,11 +909,13 @@ BF0FA6DA2DDC5CB600C9E5F2 /* VPLoginManager.swift in Sources */, BF0FA74A2DDF04E200C9E5F2 /* VPPlayerProtocol.swift in Sources */, BF0FA72C2DDD7B7300C9E5F2 /* VPHomeRankingContentCell.swift in Sources */, + BF0FA7942DE16E9300C9E5F2 /* JXTagView.swift in Sources */, BF0FA7652DE00A0E00C9E5F2 /* VPDetailPlayerControlView.swift in Sources */, BF0FA7342DDEC74500C9E5F2 /* VPCategoryModel.swift in Sources */, 1B056E4F2DDAC7FC007EE38D /* VPNavigationController.swift in Sources */, 1B056E3F2DDAC2DB007EE38D /* VPCryptorService.swift in Sources */, BF0FA7202DDC83AE00C9E5F2 /* JXButton.swift in Sources */, + BF0FA7912DE16CBF00C9E5F2 /* VPSearchHistoryView.swift in Sources */, BF0FA7072DDC687C00C9E5F2 /* VPHomeDataItem.swift in Sources */, BF0FA7452DDF027900C9E5F2 /* VPPlayer.swift in Sources */, BF0FA70E2DDC6ACC00C9E5F2 /* VPHomeItemContentCell.swift in Sources */, @@ -836,10 +924,14 @@ BF0FA76B2DE0533400C9E5F2 /* VPEpisodeMenuView.swift in Sources */, 1B056E6A2DDAD0BF007EE38D /* VPLocalizedManager.swift in Sources */, BF0FA7242DDC888F00C9E5F2 /* VPHomeRecommandContentCell.swift in Sources */, + BF0FA78F2DE16B2A00C9E5F2 /* VPUserDefaultsKey.swift in Sources */, BF0FA7192DDC7F4900C9E5F2 /* VPHomeBannerCell.swift in Sources */, BF0FA7712DE062EB00C9E5F2 /* VPVideoRateModel.swift in Sources */, BF0FA7022DDC667C00C9E5F2 /* VPVideoInfoModel.swift in Sources */, + BF0FA77B2DE0788A00C9E5F2 /* UIStackView+VPAdd.swift in Sources */, BF0FA76F2DE062A700C9E5F2 /* VPRateSelectedView.swift in Sources */, + BF0FA7772DE0735800C9E5F2 /* VPSearchInputView.swift in Sources */, + BF0FA7892DE161F200C9E5F2 /* VPSearchResultView.swift in Sources */, 1B056E792DDB365A007EE38D /* VPTabBarItemSelectedView.swift in Sources */, 1B056E722DDB022F007EE38D /* VPTabBarItem.swift in Sources */, 1B056E412DDAC30A007EE38D /* VPModel.swift in Sources */, diff --git a/Veloria/Base/Controller/VPTabBarController.swift b/Veloria/Base/Controller/VPTabBarController.swift index a25fec9..1d25315 100644 --- a/Veloria/Base/Controller/VPTabBarController.swift +++ b/Veloria/Base/Controller/VPTabBarController.swift @@ -88,7 +88,7 @@ extension VPTabBarController { let nav1 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected")) let nav2 = createNavigationController(viewController: VPExploreViewController(), title: "Explore".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected")) - let nav3 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_03_selected")) + let nav3 = createNavigationController(viewController: VPHomePageViewController(), title: "My List".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_03_selected")) let nav4 = createNavigationController(viewController: VPHomePageViewController(), title: "Home".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_04_selected")) viewControllers = [nav1, nav2, nav3, nav4] diff --git a/Veloria/Base/Define/VPUserDefaultsKey.swift b/Veloria/Base/Define/VPUserDefaultsKey.swift new file mode 100644 index 0000000..d700329 --- /dev/null +++ b/Veloria/Base/Define/VPUserDefaultsKey.swift @@ -0,0 +1,11 @@ +// +// VPUserDefaultsKey.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/24. +// + +import UIKit + +///搜索记录 +let kVPSearchHistoryDefaultsKey = "kVPSearchHistoryDefaultsKey" diff --git a/Veloria/Base/Extension/UIColor+VPAdd.swift b/Veloria/Base/Extension/UIColor+VPAdd.swift index 22f90d3..7b42232 100644 --- a/Veloria/Base/Extension/UIColor+VPAdd.swift +++ b/Veloria/Base/Extension/UIColor+VPAdd.swift @@ -11,6 +11,10 @@ extension UIColor { static func backgroundColor() -> UIColor { return color000000() } + + static func placeholderColor() -> UIColor { + return colorFFFFFF(alpha: 0.3) + } } extension UIColor { @@ -69,4 +73,28 @@ extension UIColor { static func color1C2D2F(alpha: CGFloat = 1) -> UIColor { return UIColor(rgb: 0x1C2D2F, alpha: alpha) } + + static func colorA87A46(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xA87A46, alpha: alpha) + } + + static func colorFBF2C7(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xFBF2C7, alpha: alpha) + } + + static func colorA8A38E(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xA8A38E, alpha: alpha) + } + + static func color555555(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0x555555, alpha: alpha) + } + + static func colorFFBFBE(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xFFBFBE, alpha: alpha) + } + + static func colorD87675(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xD87675, alpha: alpha) + } } diff --git a/Veloria/Base/Extension/UIStackView+VPAdd.swift b/Veloria/Base/Extension/UIStackView+VPAdd.swift new file mode 100644 index 0000000..5862507 --- /dev/null +++ b/Veloria/Base/Extension/UIStackView+VPAdd.swift @@ -0,0 +1,20 @@ +// +// UIStackView+VPAdd.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/23. +// + +import UIKit + +extension UIStackView { + + func removeAllArrangedSubview() { + let arrangedSubviews = self.arrangedSubviews + + arrangedSubviews.forEach { + self.removeArrangedSubview($0) + $0.removeFromSuperview() + } + } +} diff --git a/Veloria/Base/Networking/API/VPHomeAPI.swift b/Veloria/Base/Networking/API/VPHomeAPI.swift index aa9a70e..a5cc769 100644 --- a/Veloria/Base/Networking/API/VPHomeAPI.swift +++ b/Veloria/Base/Networking/API/VPHomeAPI.swift @@ -19,4 +19,38 @@ class VPHomeAPI: NSObject { completer?(response.data?.list) } } + + ///热门搜索 + static func requestHotSearchList(completer: ((_ list: [VPShortModel]?) -> Void)?) { + var param = VPNetworkParameters(path: "/search/hots") + param.method = .get + + VPNetwork.request(parameters: param) { (response: VPNetworkResponse>) in + completer?(response.data?.list) + } + } + + ///搜索排行 + static func requestTopSearchList(completer: ((_ list: [VPShortModel]?) -> Void)?) { + var param = VPNetworkParameters(path: "/getVisitTop") + param.method = .get + + VPNetwork.request(parameters: param) { (response: VPNetworkResponse<[VPShortModel]>) in + completer?(response.data) + } + } + + ///搜索 + static func requestSearch(text: String, completer: ((_ list: [VPShortModel]?) -> Void)?) { + var param = VPNetworkParameters(path: "/search") + param.method = .get + param.parameters = [ + "search" : text + ] + + VPNetwork.request(parameters: param) { (response: VPNetworkResponse>) in + completer?(response.data?.list) + } + + } } diff --git a/Veloria/Base/View/VPGradientLabel.swift b/Veloria/Base/View/VPGradientLabel.swift new file mode 100644 index 0000000..efddd46 --- /dev/null +++ b/Veloria/Base/View/VPGradientLabel.swift @@ -0,0 +1,51 @@ +// +// VPGradientLabel.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/24. +// + +import UIKit + +class VPGradientLabel: UILabel { + + private(set) var gradientLayer: CAGradientLayer = { + // 创建渐变层 + let gradient = CAGradientLayer() + gradient.colors = [ + UIColor.red.cgColor, + UIColor.blue.cgColor + ] + gradient.startPoint = CGPoint(x: 0, y: 0.5) + gradient.endPoint = CGPoint(x: 1, y: 0.5) + gradient.locations = [0, 1] + return gradient + }() + + override init(frame: CGRect) { + super.init(frame: frame) + layer.addSublayer(gradientLayer) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + + gradientLayer.frame = bounds + + // 创建一个文字图层 + let textLayer = CATextLayer() + textLayer.string = attributedText ?? NSAttributedString(string: text ?? "") + textLayer.frame = bounds + textLayer.alignmentMode = .center + textLayer.contentsScale = UIScreen.main.scale + + gradientLayer.mask = textLayer + + } + +} diff --git a/Veloria/Base/View/VPTextField.swift b/Veloria/Base/View/VPTextField.swift new file mode 100644 index 0000000..f2db69d --- /dev/null +++ b/Veloria/Base/View/VPTextField.swift @@ -0,0 +1,126 @@ +// +// VPTextField.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/24. +// + +import UIKit + +class VPTextField: UITextField { + + ///0 不限制字数 + var maxNumber = 0 + + var currentNumber = 0 + + + var textDidChange: ((_ text: String) -> Void)? + + var vp_placeholder: String? { + set { + _createAttributedPlaceholder() + let newStr = NSMutableAttributedString(string: newValue ?? "") + newStr.font = self.vp_placeholderFont + newStr.color = self.vp_placeholderColor + _attributedPlaceholder = newStr + self.attributedPlaceholder = _attributedPlaceholder + } + get { + return _attributedPlaceholder?.string + } + } + + var vp_placeholderFont: UIFont? { + + set { + _createAttributedPlaceholder() + _attributedPlaceholder?.font = newValue + self.attributedPlaceholder = _attributedPlaceholder + } + get { + if let font = _attributedPlaceholder?.font { + return font + } else { + return self.font + } + } + } + + var vp_placeholderColor: UIColor? { + set { + _createAttributedPlaceholder() + _attributedPlaceholder?.color = newValue + self.attributedPlaceholder = _attributedPlaceholder + } + get { + if let color = _attributedPlaceholder?.color { + return color + } else { + return .placeholderColor() + } + } + } + + private var _attributedPlaceholder: NSMutableAttributedString? + + + private func _createAttributedPlaceholder() { + if self._attributedPlaceholder == nil { + _attributedPlaceholder = NSMutableAttributedString(string: "") + _attributedPlaceholder?.font = self.font + _attributedPlaceholder?.color = .placeholderColor() + self.attributedPlaceholder = _attributedPlaceholder + } + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override init(frame: CGRect) { + super.init(frame: frame) + NotificationCenter.default.addObserver(self, selector: #selector(textDidChangeNotification(sender:)), name: UITextField.textDidChangeNotification, object: nil) + +// var iq = self.iq +// iq.enableMode = .enabled + + self.textColor = .colorFFFFFF() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension VPTextField { + @objc func textDidChangeNotification(sender: Notification){ + + guard let object = sender.object else { return } + if !(object is UITextField) {return} + if (object as! UITextField) != self {return} + + + let selectedRange: UITextRange? = self.markedTextRange + + + if selectedRange == nil { + + if maxNumber > 0 { + if self.text?.count ?? 0 > self.maxNumber { + var string = self.text + string?.removeLast(self.text!.count - self.maxNumber) + self.text = string + } + } + if let textDidChange = textDidChange { + textDidChange(self.text ?? "") + } + + + self.currentNumber = self.text?.count ?? 0 + } + + } +} diff --git a/Veloria/Class/Home/Controller/VPHomePageViewController.swift b/Veloria/Class/Home/Controller/VPHomePageViewController.swift index 1973aaa..4718eff 100644 --- a/Veloria/Class/Home/Controller/VPHomePageViewController.swift +++ b/Veloria/Class/Home/Controller/VPHomePageViewController.swift @@ -111,6 +111,7 @@ class VPHomePageViewController: VPViewController { private lazy var searchButton: VPHomeSearchButton = { let button = VPHomeSearchButton() + button.addTarget(self, action: #selector(handleSearchButton), for: .touchUpInside) return button }() @@ -164,10 +165,13 @@ class VPHomePageViewController: VPViewController { self.pageView.upSc.addSubview(menuBgView) self.pageView.upSc.sendSubviewToBack(menuBgView) - } - + @objc private func handleSearchButton() { + let vc = VPSearchViewController() + self.navigationController?.pushViewController(vc, animated: true) + } + } extension VPHomePageViewController { diff --git a/Veloria/Class/Home/Controller/VPSearchViewController.swift b/Veloria/Class/Home/Controller/VPSearchViewController.swift new file mode 100644 index 0000000..cf304f0 --- /dev/null +++ b/Veloria/Class/Home/Controller/VPSearchViewController.swift @@ -0,0 +1,111 @@ +// +// VPSearchViewController.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/23. +// + +import UIKit + +class VPSearchViewController: VPViewController { + + + private lazy var viewModel = VPSearchViewModel() + + private lazy var backButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "arrow_left_icon_01"), for: .normal) + button.addTarget(self, action: #selector(handleBack), for: .touchUpInside) + return button + }() + + private lazy var textView: VPSearchInputView = { + let view = VPSearchInputView() + view.textDidChange = { [weak self] text in + self?.textDidChange(text: text) + } + return view + }() + + private lazy var homeView: VPSearchHomeView = { + let view = VPSearchHomeView() + view.viewModel = viewModel + return view + }() + + private lazy var resultView: VPSearchResultView = { + let view = VPSearchResultView() + view.viewModel = viewModel + view.isHidden = true + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + + vp_setupUI() + vp_addAction() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + +} + +extension VPSearchViewController { + + ///文本发生变化 + func textDidChange(text: String) { + if text.count > 0 { + self.resultView.isHidden = false + self.homeView.isHidden = true + } else { + self.resultView.isHidden = true + self.homeView.isHidden = false + } + self.resultView.search(text: text) + } + +} + +extension VPSearchViewController { + + private func vp_setupUI() { + view.addSubview(backButton) + view.addSubview(textView) + view.addSubview(homeView) + view.addSubview(resultView) + + backButton.snp.makeConstraints { make in + make.left.equalToSuperview() + make.top.equalToSuperview().offset(UIScreen.statusBarHeight) + make.width.equalTo(48) + make.height.equalTo(44) + } + + textView.snp.makeConstraints { make in + make.left.equalTo(backButton.snp.right) + make.right.equalToSuperview().offset(-15) + make.centerY.equalTo(backButton) + make.height.equalTo(40) + } + + homeView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(textView.snp.bottom) + } + + resultView.snp.makeConstraints { make in + make.edges.equalTo(homeView) + } + } + + private func vp_addAction() { + self.viewModel.searchHandle = { [weak self] text in + self?.textView.text = text + self?.textDidChange(text: text) + } + } +} diff --git a/Veloria/Class/Home/View/VPSearchHistoryView.swift b/Veloria/Class/Home/View/VPSearchHistoryView.swift new file mode 100644 index 0000000..982e415 --- /dev/null +++ b/Veloria/Class/Home/View/VPSearchHistoryView.swift @@ -0,0 +1,115 @@ +// +// VPSearchHistoryView.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/24. +// + +import UIKit + +class VPSearchHistoryView: UIView { + + var historyArr: [String] = [] { + didSet { + tagView.reloadData() + } + } + + var viewModel: VPSearchViewModel? + + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .fontRegular(ofSize: 13) + label.textColor = .colorFFFFFF() + label.text = "Recent Searches".localized + return label + }() + + private lazy var deleteButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "delete_icon_01"), for: .normal) + button.addTarget(self, action: #selector(handleDeleteButton), for: .touchUpInside) + return button + }() + + private lazy var tagView: JXTagView = { + let tagView = JXTagView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 10)) + tagView.tagBackgroundColor = .colorFFFFFF(alpha: 0.1) + tagView.tagHeight = 24 + tagView.textMargin = 5 + tagView.tagCornerRadius = 12 + tagView.tagHorizontalGap = 8 + tagView.tagVerticalGap = 8 + tagView.textFont = .fontRegular(ofSize: 11) + tagView.textColor = .colorFFFFFF(alpha: 0.7) + tagView.delegate = self + tagView.dataSource = self + tagView.setContentHuggingPriority(.required, for: .horizontal) + tagView.setContentCompressionResistancePriority(.required, for: .horizontal) + return tagView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setContentHuggingPriority(.required, for: .horizontal) + setContentCompressionResistancePriority(.required, for: .horizontal) + + + vp_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func handleDeleteButton() { + self.viewModel?.cleanHistory() + } + +} + +extension VPSearchHistoryView { + + private func vp_setupUI() { + addSubview(titleLabel) + addSubview(deleteButton) + addSubview(tagView) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(15) + make.top.equalToSuperview().offset(20) + } + + deleteButton.snp.makeConstraints { make in + make.centerY.equalTo(titleLabel) + make.right.equalToSuperview().offset(-5) + make.width.equalTo(36) + make.height.equalTo(40) + } + + tagView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().offset(48) + make.bottom.equalToSuperview() + } + } + +} + +//MARK: -------------- JXTagViewDelegate JXTagViewDataSource -------------- +extension VPSearchHistoryView: JXTagViewDelegate, JXTagViewDataSource { + + func jx_tagView(tagView: JXTagView, titleForIndex index: Int) -> String? { + return historyArr[index] + } + + func jx_number(in tagView: JXTagView) -> Int { + return historyArr.count + } + + func jx_tagView(tagView: JXTagView, didSelectedTagAt index: Int) { + self.viewModel?.searchHandle?(historyArr[index]) + } + +} diff --git a/Veloria/Class/Home/View/VPSearchHomeView.swift b/Veloria/Class/Home/View/VPSearchHomeView.swift new file mode 100644 index 0000000..92b25b9 --- /dev/null +++ b/Veloria/Class/Home/View/VPSearchHomeView.swift @@ -0,0 +1,84 @@ +// +// VPSearchHomeView.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/23. +// + +import UIKit + +class VPSearchHomeView: UIView { + + var viewModel: VPSearchViewModel? { + didSet { + viewModel?.addObserver(self, forKeyPath: "historyArr", context: nil) + historyView.historyArr = viewModel?.historyArr ?? [] + updateLayout() + + historyView.viewModel = viewModel + } + } + + private lazy var stackView: UIStackView = { + let view = UIStackView(arrangedSubviews: [historyView, recommendedView]) + view.axis = .vertical + view.spacing = 30 + view.distribution = .equalSpacing + return view + }() + + private lazy var historyView: VPSearchHistoryView = { + let view = VPSearchHistoryView() + return view + }() + + private lazy var recommendedView: VPSearchRecommendedView = { + let view = VPSearchRecommendedView() + return view + }() + + deinit { + viewModel?.removeObserver(self, forKeyPath: "historyArr") + } + + override init(frame: CGRect) { + super.init(frame: frame) + + vp_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "historyArr" { + historyView.historyArr = viewModel?.historyArr ?? [] + updateLayout() + } + } + + private func updateLayout() { + stackView.removeAllArrangedSubview() + + if (viewModel?.historyArr.count ?? 0) > 0 { + stackView.addArrangedSubview(historyView) + } + stackView.addArrangedSubview(recommendedView) + } +} + +extension VPSearchHomeView { + + private func vp_setupUI() { + addSubview(stackView) + + stackView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().offset(20) + make.bottom.equalToSuperview() + } + + } + +} diff --git a/Veloria/Class/Home/View/VPSearchInputView.swift b/Veloria/Class/Home/View/VPSearchInputView.swift new file mode 100644 index 0000000..280bb41 --- /dev/null +++ b/Veloria/Class/Home/View/VPSearchInputView.swift @@ -0,0 +1,76 @@ +// +// VPSearchInputView.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/23. +// + +import UIKit + +class VPSearchInputView: UIView { + + var textDidChange: ((_ text: String) -> Void)? + + var text: String? { + set { + textField.text = newValue + } + get { + return textField.text + } + } + + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "search_icon_01")) + return imageView + }() + + private lazy var textField: VPTextField = { + let textField = VPTextField() + textField.font = .fontRegular(ofSize: 14) + textField.vp_placeholder = "kSearchPlaceholderText2".localized + textField.vp_placeholderFont = textField.font + textField.textDidChange = { [weak self] text in + self?.textDidChange?(text) + } + return textField + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + vp_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + + +extension VPSearchInputView { + + private func vp_setupUI() { + layer.cornerRadius = 6 + layer.masksToBounds = true + layer.borderWidth = 0.7 + layer.borderColor = UIColor.colorFFFFFF(alpha: 0.1).cgColor + backgroundColor = UIColor.colorFFFFFF(alpha: 0.05) + + addSubview(iconImageView) + addSubview(textField) + + iconImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(10) + } + + textField.snp.makeConstraints { make in + make.top.bottom.equalToSuperview() + make.left.equalToSuperview().offset(45) + make.right.equalToSuperview().offset(-10) + } + } + +} diff --git a/Veloria/Class/Home/View/VPSearchRecommendedCell.swift b/Veloria/Class/Home/View/VPSearchRecommendedCell.swift new file mode 100644 index 0000000..96b53fa --- /dev/null +++ b/Veloria/Class/Home/View/VPSearchRecommendedCell.swift @@ -0,0 +1,142 @@ +// +// VPSearchRecommendedCell.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/24. +// + +import UIKit + +class VPSearchRecommendedCell: VPCollectionViewCell { + + var model: VPShortModel? { + didSet { + coverImageView.vp_setImage(url: model?.image_url) + titleLabel.text = model?.name + desLabel.text = model?.vp_description + + if let category = model?.category?.first, !category.isEmpty { + tagView.isHidden = false + tagView.setTitle(category, for: .normal) + } else { + tagView.isHidden = true + } + } + } + + var row: Int = 0 { + didSet { + if row < 3 { + numBgView.image = UIImage(named: "num_icon_01") + } else { + numBgView.image = UIImage(named: "num_icon_02") + } + numLabel.text = "\(row + 1)" + } + } + + private lazy var coverImageView: VPImageView = { + let imageView = VPImageView() + imageView.layer.cornerRadius = 6 + imageView.layer.masksToBounds = true + return imageView + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .fontMedium(ofSize: 12) + label.textColor = .colorFFFFFF() + label.numberOfLines = 2 + return label + }() + + private lazy var tagView: UIButton = { + let view = JXButton(type: .custom) + view.isUserInteractionEnabled = false + view.jx_font = .fontRegular(ofSize: 10) + view.setTitleColor(.colorAFAFAF(), for: .normal) + view.backgroundColor = .colorFFFFFF(alpha: 0.1) + view.layer.cornerRadius = 3 + view.layer.masksToBounds = true + view.leftAndRightMargin = 5 + return view + }() + + private lazy var desLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 2 + label.font = .fontRegular(ofSize: 10) + label.textColor = .colorFFFFFF(alpha: 0.6) + return label + }() + + private lazy var numBgView: UIImageView = { + let view = UIImageView() + return view + }() + + private lazy var numLabel: UILabel = { + let label = UILabel() + label.font = .numberFont(ofSize: 18) + label.textColor = .colorFFFFFB() + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + vp_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + + +extension VPSearchRecommendedCell { + + private func vp_setupUI() { + contentView.addSubview(coverImageView) + coverImageView.addSubview(numBgView) + coverImageView.addSubview(numLabel) + contentView.addSubview(titleLabel) + contentView.addSubview(tagView) + contentView.addSubview(desLabel) + + coverImageView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.top.bottom.equalToSuperview() + make.width.equalTo(68) + } + + numBgView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.top.equalToSuperview() + } + + numLabel.snp.makeConstraints { make in + make.centerX.equalTo(numBgView).offset(2) + make.centerY.equalTo(numBgView).offset(2) + } + + titleLabel.snp.makeConstraints { make in + make.left.equalTo(coverImageView.snp.right).offset(10) + make.right.lessThanOrEqualToSuperview() + make.centerY.equalTo(coverImageView.snp.top).offset(15) + } + + tagView.snp.makeConstraints { make in + make.left.equalTo(titleLabel) + make.top.equalToSuperview().offset(39) + make.height.equalTo(16) + } + + desLabel.snp.makeConstraints { make in + make.left.equalTo(titleLabel) + make.right.lessThanOrEqualToSuperview() + make.bottom.equalToSuperview().offset(-3) + } + } + +} diff --git a/Veloria/Class/Home/View/VPSearchRecommendedContentView.swift b/Veloria/Class/Home/View/VPSearchRecommendedContentView.swift new file mode 100644 index 0000000..1b1181e --- /dev/null +++ b/Veloria/Class/Home/View/VPSearchRecommendedContentView.swift @@ -0,0 +1,170 @@ +// +// VPSearchRecommendedContentView.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/24. +// + +import UIKit + +class VPSearchRecommendedContentView: VPGradientView { + + override var intrinsicContentSize: CGSize { + return .init(width: 256, height: 500) + } + + var dataArr: [VPShortModel] = [] { + didSet { + self.collectionView.reloadData() + } + } + + var iconImage: UIImage? { + didSet { + iconImageView.image = iconImage + } + } + + var title: String? { + didSet { + titleLabel.text = title + } + } + + var borderColors: [CGColor] = [] { + didSet { + borderGradientLayout.colors = borderColors + } + } + + var titleColors: [CGColor] = [] { + didSet { + titleLabel.gradientLayer.colors = titleColors + } + } + + + + ///渐变边框 + private lazy var borderGradientLayout: CAGradientLayer = { + let layer = CAGradientLayer() + layer.locations = [0, 0.1, 0.3, 1] + layer.startPoint = .init(x: 0.5, y: 0) + layer.endPoint = .init(x: 0.5, y: 1) + layer.mask = borderShapeLayer + return layer + }() + + private lazy var borderShapeLayer: CAShapeLayer = { + let shapeLayer = CAShapeLayer() + shapeLayer.lineWidth = 1 + shapeLayer.fillColor = UIColor.clear.cgColor + shapeLayer.strokeColor = UIColor.black.cgColor + return shapeLayer + }() + + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView() + return imageView + }() + + private lazy var titleLabel: VPGradientLabel = { + let label = VPGradientLabel() + label.font = .fontBold(ofSize: 14) + return label + }() + + private lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.itemSize = .init(width: intrinsicContentSize.width - 20, height: 90) + layout.sectionInset = .init(top: 0, left: 10, bottom: 0, right: 10) + layout.minimumLineSpacing = 10 + return layout + }() + + private lazy var collectionView: VPCollectionView = { + let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsVerticalScrollIndicator = false + collectionView.register(VPSearchRecommendedCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + colors = [UIColor.colorA87A46(alpha: 0.18).cgColor, UIColor.colorA87A46(alpha: 0.05).cgColor, UIColor.colorA87A46(alpha: 0).cgColor] + locations = [0, 0.5, 1] + startPoint = .init(x: 0.5, y: 0) + endPoint = .init(x: 0.5, y: 1) + layer.cornerRadius = 10 + layer.masksToBounds = true + + self.layer.addSublayer(borderGradientLayout) + + vp_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + override func layoutSubviews() { + super.layoutSubviews() + + borderGradientLayout.frame = self.bounds + let path = UIBezierPath(roundedRect: bounds.insetBy(dx: borderShapeLayer.lineWidth / 2, dy: borderShapeLayer.lineWidth / 2), cornerRadius: layer.cornerRadius) + borderShapeLayer.path = path.cgPath + + } + +} + +extension VPSearchRecommendedContentView { + private func vp_setupUI() { + addSubview(iconImageView) + addSubview(titleLabel) + addSubview(collectionView) + + iconImageView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(10) + make.top.equalToSuperview().offset(10) + make.width.height.equalTo(18) + } + + titleLabel.snp.makeConstraints { make in + make.centerY.equalTo(iconImageView) + make.left.equalTo(iconImageView.snp.right).offset(4) + } + + collectionView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.top.equalToSuperview().offset(39) + make.bottom.equalToSuperview().offset(-10) + } + } +} + +//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource -------------- +extension VPSearchRecommendedContentView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPSearchRecommendedCell + cell.model = dataArr[indexPath.row] + cell.row = indexPath.row + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = dataArr[indexPath.row] + let vc = VPDetailPlayerViewController() + vc.shortPlayId = model.short_play_id + self.viewController?.navigationController?.pushViewController(vc, animated: true) + } + +} diff --git a/Veloria/Class/Home/View/VPSearchRecommendedView.swift b/Veloria/Class/Home/View/VPSearchRecommendedView.swift new file mode 100644 index 0000000..fda69e0 --- /dev/null +++ b/Veloria/Class/Home/View/VPSearchRecommendedView.swift @@ -0,0 +1,122 @@ +// +// VPSearchRecommendedView.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/23. +// + +import UIKit + +class VPSearchRecommendedView: UIView { + + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: UIScreen.height) + } + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .fontMedium(ofSize: 15) + label.textColor = .colorFFFFFF() + label.text = "Recommended For You".localized + return label + }() + + private lazy var scrollView: VPScrollView = { + let scrollView = VPScrollView() + scrollView.showsHorizontalScrollIndicator = false + return scrollView + }() + + private lazy var stackView: UIStackView = { + let view = UIStackView(arrangedSubviews: [topView, newView]) + view.axis = .horizontal + view.spacing = 20 + return view + }() + + private lazy var topView: VPSearchRecommendedContentView = { + let view = VPSearchRecommendedContentView() + view.borderColors = [UIColor.colorFBF2C7(alpha: 0.6).cgColor, UIColor.colorA8A38E(alpha: 0.4).cgColor, UIColor.color555555(alpha: 0.3).cgColor, UIColor.colorFBF2C7(alpha: 0.05).cgColor] + view.titleColors = [UIColor.colorFBF2C7().cgColor, UIColor.colorA87A46().cgColor] + view.iconImage = UIImage(named: "top_icon_01") + view.title = "Trending Top 10".localized + return view + }() + + private lazy var newView: VPSearchRecommendedContentView = { + let view = VPSearchRecommendedContentView() + view.borderColors = [UIColor.colorFFBFBE(alpha: 0.6).cgColor, UIColor.colorFFBFBE(alpha: 0.4).cgColor, UIColor.color555555(alpha: 0.3).cgColor, UIColor.colorFFBFBE(alpha: 0.05).cgColor] + view.titleColors = [UIColor.colorFFBFBE().cgColor, UIColor.colorD87675().cgColor] + view.iconImage = UIImage(named: "hot_icon_02") + view.title = "Latest Trends".localized + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + vp_setupUI() + + requestTopData() + requestHotData() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + +} + +extension VPSearchRecommendedView { + + private func vp_setupUI() { + addSubview(titleLabel) + addSubview(scrollView) + scrollView.addSubview(stackView) + + titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview() + make.left.equalToSuperview().offset(15) + } + + scrollView.snp.makeConstraints { make in + make.left.right.equalToSuperview() + make.bottom.equalToSuperview() + make.top.equalToSuperview().offset(30) + } + + stackView.snp.makeConstraints { make in + make.left.equalToSuperview().offset(15) + make.right.equalToSuperview().offset(-15) + make.top.equalToSuperview() + make.height.equalTo(self).offset(-(30 + UIScreen.tabbarSafeBottomMargin)) + } + } + +} + + +extension VPSearchRecommendedView { + + private func requestTopData() { + VPHomeAPI.requestTopSearchList { [weak self] list in + guard let self = self else { return } + if let list = list { + self.topView.dataArr = list + } + } + } + + private func requestHotData() { + + VPHomeAPI.requestHotSearchList { [weak self] list in + guard let self = self else { return } + if let list = list { + self.newView.dataArr = list + } + } + + } + +} diff --git a/Veloria/Class/Home/View/VPSearchResultCell.swift b/Veloria/Class/Home/View/VPSearchResultCell.swift new file mode 100644 index 0000000..b2a3274 --- /dev/null +++ b/Veloria/Class/Home/View/VPSearchResultCell.swift @@ -0,0 +1,111 @@ +// +// VPSearchResultCell.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/24. +// + +import UIKit + +class VPSearchResultCell: VPCollectionViewCell { + + var searchText: String? { + didSet { + + } + } + + var model: VPShortModel? { + didSet { + coverImageView.vp_setImage(url: model?.image_url) + videoNameLabel.text = model?.name + desLabel.text = model?.vp_description + + 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() + imageView.layer.cornerRadius = 6 + imageView.layer.masksToBounds = true + return imageView + }() + + private lazy var videoNameLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 2 + label.font = .fontMedium(ofSize: 14) + label.textColor = .colorFFFFFF() + return label + }() + + private lazy var desLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 2 + label.font = .fontRegular(ofSize: 11) + label.textColor = .colorFFFFFF(alpha: 0.6) + return label + }() + + private lazy var hotView: UIButton = { + let view = JXButton(type: .custom) + view.isUserInteractionEnabled = false + view.titleDirection = .right + view.jx_font = .fontRegular(ofSize: 10) + view.setTitleColor(.colorDEDEDE(), for: .normal) + view.setImage(UIImage(named: "hot_icon_01"), for: .normal) + view.space = 1 + return view + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + vp_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension VPSearchResultCell { + + private func vp_setupUI() { + contentView.addSubview(coverImageView) + contentView.addSubview(videoNameLabel) + contentView.addSubview(desLabel) + contentView.addSubview(hotView) + + coverImageView.snp.makeConstraints { make in + make.left.top.bottom.equalToSuperview() + make.width.equalTo(84) + } + + videoNameLabel.snp.makeConstraints { make in + make.left.equalTo(coverImageView.snp.right).offset(12) + make.right.lessThanOrEqualToSuperview() + make.centerY.equalTo(coverImageView.snp.top).offset(21) + } + + desLabel.snp.makeConstraints { make in + make.left.equalTo(videoNameLabel) + make.right.lessThanOrEqualToSuperview() + make.bottom.equalToSuperview().offset(-3) + } + + hotView.snp.makeConstraints { make in + make.left.equalTo(videoNameLabel) + make.top.equalToSuperview().offset(51) + } + } + +} diff --git a/Veloria/Class/Home/View/VPSearchResultView.swift b/Veloria/Class/Home/View/VPSearchResultView.swift new file mode 100644 index 0000000..a0980f8 --- /dev/null +++ b/Veloria/Class/Home/View/VPSearchResultView.swift @@ -0,0 +1,115 @@ +// +// VPSearchResultView.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/24. +// + +import UIKit + +class VPSearchResultView: UIView { + + var viewModel: VPSearchViewModel? + + private lazy var dataArr: [VPShortModel] = [] + private(set) lazy var searchText: String = "" + + //MARK: UI属性 + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .fontMedium(ofSize: 13) + label.textColor = .colorFFFFFF() + label.text = "Search Results".localized + return label + }() + + private lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.itemSize = .init(width: UIScreen.width - 30, height: 108) + layout.sectionInset = .init(top: 0, left: 15, bottom: UIScreen.tabbarSafeBottomMargin + 10, right: 15) + layout.minimumLineSpacing = 10 + return layout + }() + + private lazy var collectionView: VPCollectionView = { + let collectionView = VPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.register(VPSearchResultCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + vp_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func search(text: String) { + searchText = text + requestSearch(text: text) + } + +} + +extension VPSearchResultView { + + private func vp_setupUI() { + addSubview(titleLabel) + addSubview(collectionView) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(15) + make.top.equalToSuperview().offset(20) + } + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(titleLabel.snp.bottom).offset(10) + } + } + +} + +//MARK: -------------- UICollectionViewDataSource UICollectionViewDelegate -------------- +extension VPSearchResultView: UICollectionViewDataSource, UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! VPSearchResultCell + cell.searchText = self.searchText + cell.model = dataArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.dataArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let model = dataArr[indexPath.row] + + let vc = VPDetailPlayerViewController() + vc.shortPlayId = model.short_play_id + self.viewController?.navigationController?.pushViewController(vc, animated: true) + + self.viewModel?.addHistory(text: self.searchText) + } +} + +extension VPSearchResultView { + + private func requestSearch(text: String) { + VPHomeAPI.requestSearch(text: text) { [weak self] list in + guard let self = self else { return } + if self.searchText != text { return; } + + if let list = list { + self.dataArr = list + self.collectionView.reloadData() + } + } + } + +} diff --git a/Veloria/Class/Home/ViewModel/VPSearchViewModel.swift b/Veloria/Class/Home/ViewModel/VPSearchViewModel.swift new file mode 100644 index 0000000..84543c9 --- /dev/null +++ b/Veloria/Class/Home/ViewModel/VPSearchViewModel.swift @@ -0,0 +1,60 @@ +// +// VPSearchViewModel.swift +// Veloria +// +// Created by 湖南秦九 on 2025/5/24. +// + +import UIKit + +class VPSearchViewModel: NSObject { + + @objc dynamic var historyArr: [String] = VPSearchViewModel.getSearchHistory() + + func addHistory(text: String) { + guard text.count > 0 else { return } + + VPSearchViewModel.addSearchHistory(text: text) + historyArr = VPSearchViewModel.getSearchHistory() + } + + func cleanHistory() { + VPSearchViewModel.cleanSearchHistory() + historyArr.removeAll() + } + + var searchHandle: ((_ text: String) -> Void)? + +} + + +extension VPSearchViewModel { + ///添加历史记录 + static func addSearchHistory(text: String) { + var arr = getSearchHistory() + for (i, value) in arr.enumerated() { + if value == text { + arr.remove(at: i) + break + } + } + arr.insert(text, at: 0) + if arr.count > 10 { + arr.removeLast() + } + UserDefaults.standard.set(arr, forKey: kVPSearchHistoryDefaultsKey) + UserDefaults.standard.synchronize() + } + + ///获取历史记录 + static func getSearchHistory() -> [String] { + let arr = UserDefaults.standard.object(forKey: kVPSearchHistoryDefaultsKey) as? [String] + return arr ?? [] + } + + ///清空历史记录 + static func cleanSearchHistory() { + UserDefaults.standard.set([], forKey: kVPSearchHistoryDefaultsKey) + UserDefaults.standard.synchronize() + } +} diff --git a/Veloria/Class/Player/Model/VPShortModel.swift b/Veloria/Class/Player/Model/VPShortModel.swift index adf93ee..59becdb 100644 --- a/Veloria/Class/Player/Model/VPShortModel.swift +++ b/Veloria/Class/Player/Model/VPShortModel.swift @@ -19,7 +19,7 @@ class VPShortModel: VPModel, SmartCodable { var all_coins: String? var buy_type: String? var collect_total: Int? - var sp_description: String? + var vp_description: String? var episode_total: Int? var horizontally_img: String? var image_url: String? @@ -41,12 +41,12 @@ class VPShortModel: VPModel, SmartCodable { @IgnoredKey var titleAttributedString: NSAttributedString? @IgnoredKey - var sp_isSelected: Bool? + var vp_isSelected: Bool? static func mappingForKey() -> [SmartKeyTransformer]? { return [ - CodingKeys.sp_description <--- ["description"] + CodingKeys.vp_description <--- ["description"] ] } } diff --git a/Veloria/Class/Player/View/VPEpisodeView.swift b/Veloria/Class/Player/View/VPEpisodeView.swift index e66ae84..e8c02be 100644 --- a/Veloria/Class/Player/View/VPEpisodeView.swift +++ b/Veloria/Class/Player/View/VPEpisodeView.swift @@ -25,7 +25,7 @@ class VPEpisodeView: HWPanModalContentView { } else { tagView.isHidden = true } - desLabel.text = shortModel?.sp_description + desLabel.text = shortModel?.vp_description } } diff --git a/Veloria/Class/Player/View/VPPlayerProgressView.swift b/Veloria/Class/Player/View/VPPlayerProgressView.swift index 2ace123..611c3e4 100644 --- a/Veloria/Class/Player/View/VPPlayerProgressView.swift +++ b/Veloria/Class/Player/View/VPPlayerProgressView.swift @@ -188,7 +188,7 @@ extension VPPlayerProgressView { case .changed: let point = sender.translation(in: self) - let offsetX = point.x / self.width + let offsetX = point.x / (self.width - self.insets.left - self.insets.right) self.panProgress = self.tempProgress + offsetX if self.panProgress < 0 { self.panProgress = 0 @@ -206,7 +206,7 @@ extension VPPlayerProgressView { @objc func handleTapGesture(sender: UITapGestureRecognizer) { let point = sender.location(in: self) - let offsetX = point.x / self.width + let offsetX = (point.x - self.insets.left) / (self.width - self.insets.left - self.insets.right) self.panFinish?(offsetX) } } diff --git a/Veloria/Source/Assets.xcassets/icon/delete_icon_01.imageset/Contents.json b/Veloria/Source/Assets.xcassets/icon/delete_icon_01.imageset/Contents.json new file mode 100644 index 0000000..5c4d3b1 --- /dev/null +++ b/Veloria/Source/Assets.xcassets/icon/delete_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/delete_icon_01.imageset/Frame@2x.png b/Veloria/Source/Assets.xcassets/icon/delete_icon_01.imageset/Frame@2x.png new file mode 100644 index 0000000..a4e5e8d Binary files /dev/null and b/Veloria/Source/Assets.xcassets/icon/delete_icon_01.imageset/Frame@2x.png differ diff --git a/Veloria/Source/Assets.xcassets/icon/delete_icon_01.imageset/Frame@3x.png b/Veloria/Source/Assets.xcassets/icon/delete_icon_01.imageset/Frame@3x.png new file mode 100644 index 0000000..12b3a68 Binary files /dev/null and b/Veloria/Source/Assets.xcassets/icon/delete_icon_01.imageset/Frame@3x.png differ diff --git a/Veloria/Source/Assets.xcassets/icon/hot_icon_02.imageset/Contents.json b/Veloria/Source/Assets.xcassets/icon/hot_icon_02.imageset/Contents.json new file mode 100644 index 0000000..5c4d3b1 --- /dev/null +++ b/Veloria/Source/Assets.xcassets/icon/hot_icon_02.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_02.imageset/Frame@2x.png b/Veloria/Source/Assets.xcassets/icon/hot_icon_02.imageset/Frame@2x.png new file mode 100644 index 0000000..b6b4880 Binary files /dev/null and b/Veloria/Source/Assets.xcassets/icon/hot_icon_02.imageset/Frame@2x.png differ diff --git a/Veloria/Source/Assets.xcassets/icon/hot_icon_02.imageset/Frame@3x.png b/Veloria/Source/Assets.xcassets/icon/hot_icon_02.imageset/Frame@3x.png new file mode 100644 index 0000000..cf3599d Binary files /dev/null and b/Veloria/Source/Assets.xcassets/icon/hot_icon_02.imageset/Frame@3x.png differ diff --git a/Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Contents.json b/Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Contents.json new file mode 100644 index 0000000..d9d9a88 --- /dev/null +++ b/Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Ellipse 4@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Ellipse 4@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Ellipse 4@2x.png b/Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Ellipse 4@2x.png new file mode 100644 index 0000000..9505334 Binary files /dev/null and b/Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Ellipse 4@2x.png differ diff --git a/Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Ellipse 4@3x.png b/Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Ellipse 4@3x.png new file mode 100644 index 0000000..793d88b Binary files /dev/null and b/Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Ellipse 4@3x.png differ diff --git a/Veloria/Source/Assets.xcassets/icon/top_icon_01.imageset/Contents.json b/Veloria/Source/Assets.xcassets/icon/top_icon_01.imageset/Contents.json new file mode 100644 index 0000000..5c4d3b1 --- /dev/null +++ b/Veloria/Source/Assets.xcassets/icon/top_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/top_icon_01.imageset/Frame@2x.png b/Veloria/Source/Assets.xcassets/icon/top_icon_01.imageset/Frame@2x.png new file mode 100644 index 0000000..4e71720 Binary files /dev/null and b/Veloria/Source/Assets.xcassets/icon/top_icon_01.imageset/Frame@2x.png differ diff --git a/Veloria/Source/Assets.xcassets/icon/top_icon_01.imageset/Frame@3x.png b/Veloria/Source/Assets.xcassets/icon/top_icon_01.imageset/Frame@3x.png new file mode 100644 index 0000000..123f9ae Binary files /dev/null and b/Veloria/Source/Assets.xcassets/icon/top_icon_01.imageset/Frame@3x.png differ diff --git a/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03.imageset/Frame@2x.png b/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03.imageset/Frame@2x.png index 42f5999..12c58ae 100644 Binary files a/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03.imageset/Frame@2x.png and b/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03.imageset/Frame@2x.png differ diff --git a/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03.imageset/Frame@3x.png b/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03.imageset/Frame@3x.png index e4246ba..7cb59fd 100644 Binary files a/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03.imageset/Frame@3x.png and b/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03.imageset/Frame@3x.png differ diff --git a/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03_selected.imageset/Frame@2x.png b/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03_selected.imageset/Frame@2x.png index 74008e5..520b18d 100644 Binary files a/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03_selected.imageset/Frame@2x.png and b/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03_selected.imageset/Frame@2x.png differ diff --git a/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03_selected.imageset/Frame@3x.png b/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03_selected.imageset/Frame@3x.png index 95c5cec..7a94f63 100644 Binary files a/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03_selected.imageset/Frame@3x.png and b/Veloria/Source/Assets.xcassets/tabbar/tabbar_icon_03_selected.imageset/Frame@3x.png differ diff --git a/Veloria/Source/en.lproj/Localizable.strings b/Veloria/Source/en.lproj/Localizable.strings index f0cdf23..f4e2c33 100644 --- a/Veloria/Source/en.lproj/Localizable.strings +++ b/Veloria/Source/en.lproj/Localizable.strings @@ -12,9 +12,15 @@ "Explore" = "Explore"; "EP.%@" = "EP.%@"; "All %@ Episodes" = "All %@ Episodes"; - +"Recommended For You" = "Recommended For You"; +"Trending Top 10" = "Trending Top 10"; +"Latest Trends" = "Latest Trends"; +"Search Results" = "Search Results"; +"Recent Searches" = "Recent Searches"; +"My List" = "My List"; "kHomeTitleText" = "10,000+ addictive shorts await!"; "kSearchPlaceholderText1" = "Search dramas"; +"kSearchPlaceholderText2" = "#Recersal of fate"; "kHomeMenuTitle" = "Select Categories"; diff --git a/Veloria/Thirdparty/JXTagView/JXTagView.swift b/Veloria/Thirdparty/JXTagView/JXTagView.swift new file mode 100644 index 0000000..d2a61f2 --- /dev/null +++ b/Veloria/Thirdparty/JXTagView/JXTagView.swift @@ -0,0 +1,307 @@ +// +// JXTagView.swift +// YDLive +// +// Created by 曾觉新 on 2020/11/7. +// + +import UIKit + +@objc protocol JXTagViewDataSource: NSObjectProtocol { + + @objc optional func jx_tagView(tagView: JXTagView, titleForIndex index: Int) -> String? + @objc optional func jx_tagView(tagView: JXTagView, attributedTitleForIndex index: Int) -> NSAttributedString? + func jx_number(in tagView: JXTagView) -> Int + +} + +@objc protocol JXTagViewDelegate: NSObjectProtocol { + + @objc optional func jx_tagView(tagView: JXTagView, didSelectedTagAt index: Int) + + @objc optional func jx_tagView(tagView: JXTagView, enableForIndex index: Int) -> Bool + + @objc optional func jx_tagView(tagView: JXTagView, selectedForIndex index: Int) -> Bool +} + +class JXTagView: UIView { + enum LayoutDirection: Int { + case vertical = 0 + case horizontal = 1 + } + + + weak var delegate: JXTagViewDelegate? + weak var dataSource: JXTagViewDataSource? + + var layoutDirection: LayoutDirection = .vertical + + var tagBackgroundColor: UIColor = .systemBackground + var tagSelectedBackgroundColor: UIColor = .systemBackground + var tagDisabledBackgroundColor: UIColor = .systemBackground + + var textFont: UIFont = .systemFont(ofSize: 14) + + var textColor: UIColor = .colorFFFFFF() + var textSelectedColor: UIColor = .colorFFFFFF() + var textDisabledColor: UIColor = .gray + + var tagCornerRadius: CGFloat = 14 + + var tagBorderWidth: CGFloat = 0 + var tagBorderColor: UIColor? + var tagBorderSelectedColor: UIColor? + + /// 0表示自适应 + var tagWidth: CGFloat = 0 + var tagHeight: CGFloat = 28 + var tagMinWidth: CGFloat = 0 + ///文本边距 + var textMargin: CGFloat = 0 + ///横向间隙 + var tagHorizontalGap: CGFloat = 10 + ///纵向间隙 + var tagVerticalGap: CGFloat = 10 + ///左右边距 + var leftAndRightMargin: CGFloat = 15 + ///上下边距 + var topAndBottomMargin: CGFloat = 0 + ///当前选中的标签 + private(set) var selectedIndex: Int? + + private var oneReload = true + + //MARK:-------------- 黄金分割线 -------------- + private(set) var buttonArr: [UIButton] = [] + + private var contentHeight: CGFloat = 0 + + override var intrinsicContentSize: CGSize { + let height = (self.buttonArr.last?.frame.maxY ?? 0) + self.topAndBottomMargin + if layoutDirection == .vertical { + return CGSize(width: UIScreen.main.bounds.size.width, height: height) + } else { + let width = (self.buttonArr.last?.frame.maxX ?? 0) + self.leftAndRightMargin + return CGSize(width: width, height: height) + } + } + +// deinit { +// NotificationCenter.default.removeObserver(self) +// } + + override init(frame: CGRect) { + super.init(frame: frame) + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func layoutSubviews() { + super.layoutSubviews() + if oneReload { + self.reloadData() + oneReload = false + } + + self.buttonArr.enumerated().forEach { + let isEnble: Bool = self.delegate?.jx_tagView?(tagView: self, enableForIndex: $0) ?? true + let isSelected: Bool = self.delegate?.jx_tagView?(tagView: self, selectedForIndex: $0) ?? false + + let button = $1 + button.isSelected = isSelected + button.isEnabled = isEnble + + if isSelected { + button.layer.borderColor = tagBorderSelectedColor?.cgColor + } else { + button.layer.borderColor = tagBorderColor?.cgColor + } + button.setBackgroundImage(UIImage(color: tagBackgroundColor), for: .normal) + button.setBackgroundImage(UIImage(color: tagSelectedBackgroundColor), for: .selected) + button.setBackgroundImage(UIImage(color: tagSelectedBackgroundColor), for: [.highlighted, .selected]) + button.setBackgroundImage(UIImage(color: tagDisabledBackgroundColor), for: .disabled) + + } + } + + ///数据没有变化,只是变化状态 + func updateState() { + self.setNeedsLayout() + self.layoutIfNeeded() + } + + ///重置布局:当数据发生变化时,需要重置布局 + @objc func reloadData() { + selectedIndex = nil + for button in buttonArr { + button.removeFromSuperview() + } + buttonArr.removeAll() + + let number = self.dataSource?.jx_number(in: self) ?? 0 + if number == 0 { + self.invalidateIntrinsicContentSize() + return + } + + for index in 0...(number - 1) { + let text = self.dataSource?.jx_tagView?(tagView: self, titleForIndex: index) + let attributedTitle = self.dataSource?.jx_tagView?(tagView: self, attributedTitleForIndex: index) + let isEnble: Bool = self.delegate?.jx_tagView?(tagView: self, enableForIndex: index) ?? true + let isSelected: Bool = self.delegate?.jx_tagView?(tagView: self, selectedForIndex: index) ?? false + + + let button = UIButton(type: .custom) + button.tag = index + button.setTitle(text, for: .normal) + button.setAttributedTitle(attributedTitle, for: .normal) + button.setTitleColor(textColor, for: .normal) + button.setTitleColor(textSelectedColor, for: .selected) + button.setTitleColor(textSelectedColor, for: [.highlighted, .selected]) + button.setTitleColor(textDisabledColor, for: .disabled) + button.titleLabel?.font = textFont + button.layer.cornerRadius = tagCornerRadius + button.layer.masksToBounds = true + button.layer.borderWidth = tagBorderWidth + button.addTarget(self, action: #selector(handleButton(sender:)), for: .touchUpInside) + button.isEnabled = isEnble + button.titleLabel?.lineBreakMode = .byTruncatingTail + button.titleEdgeInsets = UIEdgeInsets(top: 0, left: textMargin, bottom: 0, right: textMargin) + + //修改选中状态 + button.isSelected = isSelected + + if isSelected { + selectedIndex = index + } + + self.addSubview(button) + self.buttonArr.append(button) + } + self.updateLayout() + + } + + + ///更新布局 + func updateLayout() { + var x = leftAndRightMargin + var y = topAndBottomMargin + let height = tagHeight + var width: CGFloat = 0 + + for button in buttonArr { + if tagWidth <= 0 { + if let string = button.currentTitle { + width = string.size(font: textFont).width + (textMargin * 2) + } else if let string = button.currentAttributedTitle { +// width = string.size(font: textFont).width + (textMargin * 2) + width = string.size().width + textMargin * 2 + } + + //限制宽度不能超出屏幕 + if layoutDirection == .vertical { + if width > self.width - (leftAndRightMargin * 2) { + width = self.width - (leftAndRightMargin * 2) + } + } + + //限制不能小于圆弧大小 + if width < tagCornerRadius * 2 { + width = tagCornerRadius * 2 + } + } else { + width = tagWidth + } + + if width < tagMinWidth { + width = tagMinWidth + } + + if layoutDirection == .vertical { + //判断是否需要换行 + if Float(x + width + leftAndRightMargin) > Float(self.width) { + x = leftAndRightMargin + y = y + height + tagVerticalGap + } + } + + + button.frame = CGRect(x: x, y: y, width: width, height: height) + + x = x + width + tagHorizontalGap + } + self.invalidateIntrinsicContentSize() + } + + + @objc func handleButton(sender: UIButton) { + self.delegate?.jx_tagView?(tagView: self, didSelectedTagAt: sender.tag) + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + updateState() + } + + +// private func _setButtonBackgroundColor(button: UIButton, isSelected: Bool, isEnble: Bool) { +// if !isEnble { +// button.backgroundColor = tagDisabledBackgroundColor +// } else if isSelected { +// button.backgroundColor = tagSelectedBackgroundColor +// } else { +// button.backgroundColor = tagBackgroundColor +// } +// +// if isSelected { +// button.layer.borderColor = tagBorderSelectedColor?.cgColor +// } else { +// button.layer.borderColor = tagBorderColor?.cgColor +// } +// +// } + +} + + + +extension UIButton { +// private struct AssociatedKeys { +// static var borderColors: Int? +// } + + + + func jx_setBackgroundImage(_ image: UIImage?, for state: UIControl.State) { + self.setBackgroundImage(image, for: state) + if state == .selected { + self.setBackgroundImage(image, for: [state, .highlighted]) + } + } + + func jx_setImage(_ image: UIImage?, for state: UIControl.State) { + self.setImage(image, for: state) + if state == .selected { + self.setImage(image, for: [state, .highlighted]) + } + } + + func jx_setTitle(_ title: String?, for state: UIControl.State) { + self.setTitle(title, for: state) + if state == .selected { + self.setTitle(title, for: [state, .highlighted]) + } + } + + func jx_setTitleColor(_ color: UIColor?, for state: UIControl.State) { + self.setTitleColor(color, for: state) + if state == .selected { + self.setTitleColor(color, for: [state, .highlighted]) + } + } + +} +