From 5851400e12a0b6f12d8dae061fed8a8924746847 Mon Sep 17 00:00:00 2001 From: zjx Date: Sat, 24 May 2025 13:46:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Veloria.xcodeproj/project.pbxproj | 92 ++++++ .../Base/Controller/VPTabBarController.swift | 2 +- Veloria/Base/Define/VPUserDefaultsKey.swift | 11 + Veloria/Base/Extension/UIColor+VPAdd.swift | 28 ++ .../Base/Extension/UIStackView+VPAdd.swift | 20 ++ Veloria/Base/Networking/API/VPHomeAPI.swift | 34 ++ Veloria/Base/View/VPGradientLabel.swift | 51 +++ Veloria/Base/View/VPTextField.swift | 126 +++++++ .../Controller/VPHomePageViewController.swift | 8 +- .../Controller/VPSearchViewController.swift | 111 +++++++ .../Class/Home/View/VPSearchHistoryView.swift | 115 +++++++ .../Class/Home/View/VPSearchHomeView.swift | 84 +++++ .../Class/Home/View/VPSearchInputView.swift | 76 +++++ .../Home/View/VPSearchRecommendedCell.swift | 142 ++++++++ .../View/VPSearchRecommendedContentView.swift | 170 ++++++++++ .../Home/View/VPSearchRecommendedView.swift | 122 +++++++ .../Class/Home/View/VPSearchResultCell.swift | 111 +++++++ .../Class/Home/View/VPSearchResultView.swift | 115 +++++++ .../Home/ViewModel/VPSearchViewModel.swift | 60 ++++ Veloria/Class/Player/Model/VPShortModel.swift | 6 +- Veloria/Class/Player/View/VPEpisodeView.swift | 2 +- .../Player/View/VPPlayerProgressView.swift | 4 +- .../delete_icon_01.imageset/Contents.json | 22 ++ .../icon/delete_icon_01.imageset/Frame@2x.png | Bin 0 -> 484 bytes .../icon/delete_icon_01.imageset/Frame@3x.png | Bin 0 -> 637 bytes .../icon/hot_icon_02.imageset/Contents.json | 22 ++ .../icon/hot_icon_02.imageset/Frame@2x.png | Bin 0 -> 1630 bytes .../icon/hot_icon_02.imageset/Frame@3x.png | Bin 0 -> 2890 bytes .../icon/num_icon_02.imageset/Contents.json | 22 ++ .../num_icon_02.imageset/Ellipse 4@2x.png | Bin 0 -> 1629 bytes .../num_icon_02.imageset/Ellipse 4@3x.png | Bin 0 -> 2986 bytes .../icon/top_icon_01.imageset/Contents.json | 22 ++ .../icon/top_icon_01.imageset/Frame@2x.png | Bin 0 -> 1479 bytes .../icon/top_icon_01.imageset/Frame@3x.png | Bin 0 -> 2646 bytes .../tabbar_icon_03.imageset/Frame@2x.png | Bin 691 -> 912 bytes .../tabbar_icon_03.imageset/Frame@3x.png | Bin 945 -> 1270 bytes .../Frame@2x.png | Bin 382 -> 499 bytes .../Frame@3x.png | Bin 476 -> 689 bytes Veloria/Source/en.lproj/Localizable.strings | 8 +- Veloria/Thirdparty/JXTagView/JXTagView.swift | 307 ++++++++++++++++++ 40 files changed, 1883 insertions(+), 10 deletions(-) create mode 100644 Veloria/Base/Define/VPUserDefaultsKey.swift create mode 100644 Veloria/Base/Extension/UIStackView+VPAdd.swift create mode 100644 Veloria/Base/View/VPGradientLabel.swift create mode 100644 Veloria/Base/View/VPTextField.swift create mode 100644 Veloria/Class/Home/Controller/VPSearchViewController.swift create mode 100644 Veloria/Class/Home/View/VPSearchHistoryView.swift create mode 100644 Veloria/Class/Home/View/VPSearchHomeView.swift create mode 100644 Veloria/Class/Home/View/VPSearchInputView.swift create mode 100644 Veloria/Class/Home/View/VPSearchRecommendedCell.swift create mode 100644 Veloria/Class/Home/View/VPSearchRecommendedContentView.swift create mode 100644 Veloria/Class/Home/View/VPSearchRecommendedView.swift create mode 100644 Veloria/Class/Home/View/VPSearchResultCell.swift create mode 100644 Veloria/Class/Home/View/VPSearchResultView.swift create mode 100644 Veloria/Class/Home/ViewModel/VPSearchViewModel.swift create mode 100644 Veloria/Source/Assets.xcassets/icon/delete_icon_01.imageset/Contents.json create mode 100644 Veloria/Source/Assets.xcassets/icon/delete_icon_01.imageset/Frame@2x.png create mode 100644 Veloria/Source/Assets.xcassets/icon/delete_icon_01.imageset/Frame@3x.png create mode 100644 Veloria/Source/Assets.xcassets/icon/hot_icon_02.imageset/Contents.json create mode 100644 Veloria/Source/Assets.xcassets/icon/hot_icon_02.imageset/Frame@2x.png create mode 100644 Veloria/Source/Assets.xcassets/icon/hot_icon_02.imageset/Frame@3x.png create mode 100644 Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Contents.json create mode 100644 Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Ellipse 4@2x.png create mode 100644 Veloria/Source/Assets.xcassets/icon/num_icon_02.imageset/Ellipse 4@3x.png create mode 100644 Veloria/Source/Assets.xcassets/icon/top_icon_01.imageset/Contents.json create mode 100644 Veloria/Source/Assets.xcassets/icon/top_icon_01.imageset/Frame@2x.png create mode 100644 Veloria/Source/Assets.xcassets/icon/top_icon_01.imageset/Frame@3x.png create mode 100644 Veloria/Thirdparty/JXTagView/JXTagView.swift 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 0000000000000000000000000000000000000000..a4e5e8db3ad785ab8dbb43925a634e80dcb7ed7d GIT binary patch literal 484 zcmVYgiOTilzlgO{YonCL|~Vm|Q2mas(LUkUJ0=S%zD};XGn;aki(7 z;kIztbM37aUPLQH)AQc33AmTqgnbvbmg60d%<&w%9E%y05jYVp#0L_GBV&9_;1SaC z26UCWk}t@+)Wa-aANr^~1qj^*nl9u8VY-(ZuzaznU}abz0tjWtT}ZxeAJVSSWu#k< zfIE7ZkiMtWD_L*HzU!AbgY-gTwFr1fiGHg>w=@DvM?;_?&=6<{tPO!*WBez=b_!Hd zibUcQKbMCV`hiVfD$i&oz6X!T7 amdzir*LH};7c4>m0000HAR!eJ0^v{Losl~YlodmXcM`Be z0#IRx`@@u#%!kxwK zgo?kR`Ifnb_~AnQ1-u0Q2>v7DCnUh72Y5rLTQEt9=iO?o(wwBk>pUFaAR#Ua#7n|! zCCoy;2@3coDBzo*fNz2Vz6lEWCMe*Wpnz{&6c`!daYov&l>fesu;@6+h|k10!wzts zCJd zI&2g$4OD#nk%MGO-Kp5v0FP7s08)qHUQxPHy27&Xqn~+OmwA zUjY08TN0xYL#Q!I(wXzVzF+6udplE-X^C;+ZHAe9?)iAm^FHr$-V4Bg@Cxt}uI;t8 znHInaBHB6-!p(5{^d{`%+7~EVS(z9dIkG~8C#^xRz_XTzKm2F`_IT|XlmO}Ad)^4B< z1X>}C6Fd>Z=@Xq*@^Irc{0TE7*WL{jZ*JPA z5J0-+kQtFlAu(g$n12iXiP`*}pDAUs0bpSeOpgW1vWRk;r^OA6X(WlTB6`SGsmv4Rk z#llFY{vHf^JghA{M%$SR9g;6qMw7LnAN2#Q{0Z@oR%o4)IiD>p6*1z3d7QJJBEX)e z@Q~(z`Fiov5U9WI-J2IcDP)jLFNaV~(hShUMQVsBVX(|3@&v(ha&~r8=Iw{T;h`x= zriqj_sv+k&)L0`e{=B%@Zh+!0XH5tGA*c?eS| zH9~47IKYK>zPz$NWC_YZEA)79Vf6XgrzxWJ=5j=fIHdB8xw%FWCt-s@|EF4+QZrIv zEci`2wsgMk)RimcQ1;2C8H2hVCZ__gICGIE!D<=vFbwcApMqz&?NTyRr^k<&BzPCK z?i%hFSFd(Q#2z&Yz^cegB}=L-cB#g9uRJM-AYd~DdC>~6)#0_Ux-Y?w zbJD6;wHUFyaf>{3msM1>qvU|9&azd#yus8xl{G44cYWCL`lW#$4;0V*kO#G^DgpHM zNi<)%ac*vawl=bw-s*A13i*{xd&o=3qfu+``Zg13uwbcNrL-8J+Jrl^9i-aUu#L(F zRPbI~#e7=m&JZ^Suf)}ngun|fKDB4~>tmj+s3L-*@Yi_3$xzctX2gY`7~zMTb@7Xn zw_4$0zdyMA{*4=(V}si5+PHqbXt4!A0!z(TEpT7sI;wlT+bvmD!12edCx2COW&ieuA+!m~`<`F>$9480Uk3al&(hH{Z4QvtUa*ZuddU0a6# cuYckC2ksbK{smx1od5s;07*qoM6N<$g1?strvLx| literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cf3599d3fe62b99f5cb174c657bb6431dc6f8d2e GIT binary patch literal 2890 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9505334ce37379e6d60961b127620935943c13ea GIT binary patch literal 1629 zcmV-j2BP_iP)m%K-@SLH-*@KB^F?i9d&A9~nKyIJ`M&dGuGajAXLfehZEbB8 zy}iA~&6_v56n1uY>izxwu)Mq+thMo?y6i7wW=F8Zd7opTv=HOIPy{lUTAoFdb;=M=%_D*a0zHi7_G2eVqS61k#ov$s)lnd zFu}D7?1cYicw%DW9VqTAih{^WAgjuacF62sroni z&lrZM-Ftg`cc7q`2(wWt%5pZuG0QZFw+2IHv)Qb!UAxxW-rlbI`}?bznVI^u1OBw( zfq{XKq38{4bfz}=AOCnNMviTnPIH7K9u7HxrD(x|1ML>1I5^?Jm|b06wPRyracyla zo@;n=a`HBmyqk@H4@X%IiBUWr$Q+!L*99zlD+Xkahx;i5xj4c@aKw&|j)rW!=rH;! zjDEyDq!R~A8?BEfl0wfpUOHOB2=&u}GQ{Amu_bje!(lY<^DtIuh&&Rn*xlVFfM?Gk z9v&Y40>kx;R*;%#X+d*=vsQW3$TnTuaN2dy%mbY5aZ8tOhPzu6>EE5 z(S8Of{UBcu<(HeG7}rc;#j98465_1}UR3-5&( z8}uHEDieUf8HuYPH$duqX+WnnLC_OPCt9UlN=#+147Fp##ruKY^kKV@gG!QVoH=n4GZ&6v7#>Qz8_Yn~W%B zE8~_jJtw2rE3@sqr!&}xj)GgJGXBx#qupB{-1-tmn~qs%HE!0BMk^Ve>QQ=ksy+!v zzBC+DTT0NL^Z?d>?%U}srpRir#`8O=SI6)>w7(BC^p=#B?b|4!s$vIyoU=!h5y@uK z>%fw+?bvdiXtm>|x+dMA9rB;4fq4II4BJV<311@Ns~LlOiLfV{_>gq6~XdMu(PF2E-9`I+NOL znG?ye0y-SQ`s4QPgxliy_V6%j*nXlR^lC`DaYz5o^_t?X9|jul)U`C89ihVSQ9_0; z{Pf=Zy$42>$uqaGu!xQ8c$+}ffU+JO?lGx{V>C_57}@1mehs{7j}^HI7J67AvS!l0 z8gevPr)=tWcOXB|g{QQGX zTjze>;xnip=pRDtllGbVvUI64Bb$shscWV6leN@Zzn`0%`{A?=f1cs>_4R|1k&*A< z?IkD}F;SX>qSlXuXmdl6(leSwM#_wzP3A9@?bo39;dwS*`X`K{pMm%(-phDvEf*rI zqLiQ1seCqp+h5RC@6UZRxB4Qz7y8>NKlnEL9K#(L=6CmHjJ%onvHl0fUx9FaadGjt bi|G0XG@%IWZgNoP00000NkvXXu0mjfmI@f9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..793d88b6ff2dfee448c7492e380b457c70383f49 GIT binary patch literal 2986 zcmV;b3sv-qP)}SNIX&&6_vJ)~{c`rmpJ&uRVMAtn_-l)bIDx z!ootrFF$?yv>6*4OLyapT6cZQHhV`0(NUQNgbW^u)wOZ+3QeE5Ln{E(E{^mjx_=5$xjP zqJSoN0_L6uJbwH*ZP>6O&&|#8xY@UFUqX~JqP1f1BZEG5>eS}}Vha|ihz5Jv2#|Y# z7T`_OwATlNfm|bKP^1OVJ?TQ)V9VF8UE?!(R4g9~`smT4plAPe9Das8b-?g$3!Ix@ zC)h4<_qs=efSochDy>WMA3Jtz9Txct7Fg#%0U!frDs^cb zD28TI#eoW{Wxz=r11~%$x1T(D!e}H?hMpgl@AZproH=u5JtqGa9^Qz>B7jFMPM5}l zKN`$)Ugr;3DqO1o*3aWWV=m`>Zq=$)G9D3&P!_okz_N$OD^MjOS>wHX_u|f-JHxG8 zx5~(#raEAeL-6t@hS9y;i%^8BNb+@gO+LFQ?C8Ky(Ho%(bYWG7#>(|N)Z%^fHY336bg`WC zIxq3qBg1=$iiE+O7hwpaQ86Nn*qu9f>O+SP6?i5Qov=0|ySIE>(J6z>u{~c;M?e z>o+ zg@(tA?tLwwbz@Yk=uQ4C63Hn;PtzCx7mCmbDhp_{7V|s{Hlh45@C&2`_B5pL8z$kk z0RRs`=m93Ck`1uJK*r?Ill!Ig8DW2p$LLDry59ph&Nl#%0m3fFyvJsX=SEA}pd{^m zjPK9S&#y)sF<9bhEc_jw(+e2LFeEv~6T$eH=-j9tVcMfbs=UXLNQB{Y^mtPmMu0p> zaDOsSXX8ETk&M@k)q$E4Xi;H7YRGGo#rppJ`|I)S`vUsp$&;VMjd3p6f+a9c?yS?$ zNrIR@SEk>ZWDyocz28a3C?GZV(Q=4}RPG$3gU>0)G=iLlc9SBF(+1D3BWOv)J$v@- z!vkNIZ}QhBv&;bwW1u7^?^|&Y$#hw? zB&~Ps*s&m<#st*n9@PNopbDW)C@Tz)0wYW$kl9=p2aV~P0TAD-|)0h!FkDd?k zWC~hshgxnW-Jf$U58ivgzrV#B-H&V%7MDgnD6$7|Qe_0BwWv;?mUpfcVubO(B0x*n zVR8?fs^ItFaWO_E(u)lb=!n|ZamiFpW58drTfo8Epq}&IxQW_#RPss%n#r3=5lZNy zK>_oc12_JQ@`PbE)QtuX%XNHCQnv;s8li^xxc9DJy*e+cdg;=oS?qoT3Jd^H`((7K zMSA8q>~ll$dwqb!zVMjJGvGZ$OUFg+xr0jS7>s2eaP#{{i&(6UY(c!Ftnn}{8n1hxj!Z6AsK#6 zweR?IY%(&*92%o+Sq-*fENic)4_ z$lH!{$=f)*{m!1xY}7_gb$N^P+~CH$>pXAbP#~f|2#R ziH_s^rTwrI?W+Lvee&YPi+^jK8}MCb3|ky&mc*$tR>_yp*d%?V<13MYohU(NedP87hHUYOYdC1eEIDqj93o(+_`fP z0rq=Evsb{$;;JgWsH)Oy4Z-z7npHs+L=~e)Bk-(@EoOM^s6rU3#2^n zo5OqJTgx#%_gmiW+qeI_ch}x`vCwzjxK2Z_IxTNBvDI3kNU)~e6H8QMn3!d##r}RM z$yW`NKKcs?o?XCK)Sq6ta^?MzK=U{Chp9XG5XlL?5%g_DDb@v1gq>a0Py;n-Jsq0r;OV|F<#MYD>1t-`HaZut=0v%^t<807*qoM6N<$g05DuLI3~& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4e71720014044ffa166790374519850461db3985 GIT binary patch literal 1479 zcmV;&1vvVNP)5 zas@sYk$~nbAwh+_RHPzn#fbIo%zL~yZ)SIG$9L!CgoNa0?d|N&n|a^7|Fu%*^gpIx z&?G+URgxsG$WxO4CjL#5Gy7qZs1y9h7naL$^~z$bPN7LPfDZb9)k~#d84wi01YLju zfP#Q7V}ePr&=Cv-^htEy38$QLexW z;xU5&3x)uYK)MqHzb3%UYXpD*wg=+%av3#5?vv@CKes~Kydr>jzii_INW&VFXTEC_ z7^7Jz+V^(O-R-6Av(dWJ+gc4zlYPRc4jz8Lh6?L^T2_5QOJWvl5&>z*gfzAS?cD-4 z%JcBNTU#(C`h7&xVD>Q8FI|~y#;0nE04)%AM5me~VNJ|W99YvhFaiiNP0@UtHQ^87 z0HN1rrd84;z3ys+*J!dsJRyk<2bzx-#L2X*CoSYK*i_FY)*gRgB`8ftcW^)~pa~ zO1Nj&g6>#8Z&s&Oug%P~3LY`P);eUsMSnWQeEog;v6`ANNeo%E896OGH9(upRaLrF z4M0~X$`?A!|KiMhT{?M{KvfU>hgBLkvk1(iFbK5XXMUs7=jeTO9K#7vbn1xW<}#0y zq#XNGD(kaiI=U7hNtZc8S6_ZNDrV>DS|cjQ`|(6jx`hUpeKewrOt~-;tEd||3MkS_ zm()J{@Yk=`$||Y&d?e9yM8HC-%@4nAuZ6|+z3sKi<8rx2JaucRJbKaQFIt;|sr>lW z?OSyCd3xR)3hMdkH@ilgJLE@o9q2tdDf-NgF268Pa&QcfuUGLtgRTJNek8sJK^C=I zw~WShn%{Z#=BMk&w>Y&R@AX~LHLC3t|vp5UkWML$< z8>Ek2de;h2P0>9vVD2c6I>eR(6F76*49UZWb5%+_kL0ehthBDjBV5a~8002ovPDHLkV1n26zKZ|= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..123f9ae9a69be18163667983587a432cab63665f GIT binary patch literal 2646 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 Ef+>9;W&i*H literal 0 HcmV?d00001 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 42f59994513b4966289350c75885df909f1f80e1..12c58ae0867fc1de3583115dc8b69bc42114094d 100644 GIT binary patch delta 875 zcmV-x1C;!;1&{|JiBL{Q4GJ0x0000DNk~Le0000a0000a2nGNE0O0_bn2{kge*+~+ zL_t(|0qt1ZapEu(6$JQQI#|+yw}O~YhQ~q$Ybpq-AfW<61?yCR!(;edZv|m1h_?ei znc=nPIMR;d0Ff|EesX3sk|kjs-K%?ajS!zWjLfdi!C+9z=kwQSHpPjt-EQZZOlEd* zanY?*Dv@}P&}1@kHk-{aG>hWAf0$>p*>bg7oryz)#^dobc2PnzKI@-NZ()ttD$tL(kJf=i z_;Nf%<$~Z7+U%N9m6yxqjjlC^U~D0}2i2X_DIwhy;0h?KIUv~qc1`ge-q^429YOjKyAVV#umcv?@5L)$H6@qO&sGKb>Pvy7ltq< z#NER(W$7vhiwyS7#Ta$T;as|O*R7tKkWNZIy1V8`%f)GnEg5S<67xj;3i52lV~fSY zD6(`|Umb9V(wI1s5Z*4TSdVaK9vY2?k7k78z(O1ggaxb#G1{=Oe~@ID$hq2>i#}$W zv1K5fC1D9`LLj3w#+5Y}@1sAWa-;sQ(h$fF^-&@{ zD~$ifTq}1SMTn5+#NRbmtJSDE#=4Wlhb3tH@JZ-BoP=1Zd8L%JL5)Z18S7o@ZhiA-1FnT8 zUeJ?2*k=uEXjY@!xlwxK#?+PZXO1`%&iU?>;~&yDrwOI9s`dZ?002ovPDHLkV1m!G BoVWl0 delta 652 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 Ra325w002ovPDHLkV1fw>$JYP= delta 305 zcmV-10nYyO1O5V#R(}&oL_t(|0qvL34T3NbKo2Hx1l@oU7{Lv61LFiRf*ZJk8^{K- z0c>D5IIjUTG;L|s13vC0O`Eo99*{eF00I+;xTSX0eZbz6Zoo+|)L=*B6Qi{PKNP@$ zA%s3LLx@j|4$aWcNX#Lmc>o7=aOo5C2#v(2NFWkAd{5oRvVWu;^O`zIk{Z61(w~XC z4CbXJ>B_o7LPuPVa_~qEuN;=+Fbq;yA#0LS!@!iPU^(8=X^TEr$a8a}2vo3EtjV*_ zRlI-q4n=S0Y)-nQa_a8Dk4~C9b5c@5C?XY#NQEL&p@>u{B4weqO}eCl%b_1>V>auX z!UcJGVnl&DhbmZu-JQ+KWQE*6jXvOCSLCQ_S@7`!D;YpHd8W&000000NkvXXu0mjf D)$fK^ 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 95c5cecc64b5c708438f514c7a65e09c2099b24e..7a94f63ea8745ccabb3380d41282b300af0a2184 100644 GIT binary patch delta 615 zcmV-t0+{{W1F;2=Reu6TNklPi41xO?kG0FD55E0MmV92iEIIPJQ zS(cR`2?-mKePA~z$P7q8H=1Dgun)R%24xMxhIE)fHz{d~UVqeGa02z*Lw7Os@bkJ0 zD#f7-Ajb+G`!c9IS?9TP=sst4C!3~auSw#BD9K(SBC*7kWY-kBZ|V31k%%SkiG;1W zJqDLbsS`t-U2LbD4=nN-A`nBIZyHANp0>DOUfaOlQ~&Lqf*cFB6}SMh;G-83IHoMf zMo$AS6SBukZhz}05Y({=$kN4yvqt6Tc8wZ}(a$${@ZC0pWQw5yk5dvOIe{1Ly?k4>~+upLpvReC9~ zzafTsvtWIt8=K481qOI2px>|UP_s7g#&_>d2A&&kyrljb2mW|-?it6t>HCc@wxQeC z*-fW?Ey6~;+19_L(cp(UC^xK002ovPDHLkV1mTq BAb0=( delta 401 zcmV;C0dD@W1>6IWReu3-NklCZJ4koaH8^1v@rW&0Tv>I$4n&^_~=;8#{nuoHs-?;<)3u#vkDOCjNd%gOeI_ zd{E%6RFKEdXvYjvI1r;!!2>Zlf)TN7!HAd)K~A8dG*Q7Gv457~<{;wzM9x*h9f)A9(9EP!D`LPLrZl=)3hU3OL=UY^f zLBo~b|KY3gsJ*BXgS67+Ug3&%^V_mIa^N~{P9AFN3^ vMxNu?P&g}pp>Y0uY8s9O#{`C9h~x_e)8SxOE*0qj015yANkvXXu0mjf-NnD% 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]) + } + } + +} +