diff --git a/BeeReel.xcodeproj/project.pbxproj b/BeeReel.xcodeproj/project.pbxproj index 2ba7bd4..386c3b1 100644 --- a/BeeReel.xcodeproj/project.pbxproj +++ b/BeeReel.xcodeproj/project.pbxproj @@ -37,6 +37,14 @@ BF02B8222E2FAB1600172177 /* BRAboutUsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8212E2FAB1600172177 /* BRAboutUsViewController.swift */; }; BF02B8242E2FAEB500172177 /* BRAboutUsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8232E2FAEB500172177 /* BRAboutUsCell.swift */; }; BF02B8262E2FB36A00172177 /* BRAboutUsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8252E2FB36A00172177 /* BRAboutUsHeaderView.swift */; }; + BF02B8282E30821B00172177 /* BRSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8272E30821B00172177 /* BRSearchViewController.swift */; }; + BF02B82D2E30855300172177 /* BRSearchTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B82C2E30855300172177 /* BRSearchTextView.swift */; }; + BF02B82F2E30895700172177 /* BRSearchRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B82E2E30895700172177 /* BRSearchRecordView.swift */; }; + BF02B8312E30897700172177 /* BRSearchHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8302E30897700172177 /* BRSearchHomeView.swift */; }; + BF02B8332E308E4300172177 /* BRSearchRecordTagCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8322E308E4300172177 /* BRSearchRecordTagCell.swift */; }; + BF02B8362E30ACEE00172177 /* BRSearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8352E30ACEE00172177 /* BRSearchViewModel.swift */; }; + BF02B8392E30B30400172177 /* AlignedCollectionViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B8382E30B30400172177 /* AlignedCollectionViewFlowLayout.swift */; }; + BF02B83B2E30BB4C00172177 /* BRHotSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF02B83A2E30BB4C00172177 /* BRHotSearchView.swift */; }; BF0DBDD12E0D4E150035F6B4 /* BRTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0DBDD02E0D4E150035F6B4 /* BRTabBar.swift */; }; BF3338E82E15219500B10F76 /* UINavigationBar+BRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338E72E15218F00B10F76 /* UINavigationBar+BRAdd.swift */; }; BF3338EA2E152B8100B10F76 /* BRPlayerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338E92E152B8100B10F76 /* BRPlayerCache.swift */; }; @@ -48,6 +56,9 @@ BF3338F92E16178700B10F76 /* BRDetailControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338F82E16178700B10F76 /* BRDetailControlView.swift */; }; BF3338FB2E161CF900B10F76 /* NSNumber+BRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338FA2E161CF000B10F76 /* NSNumber+BRAdd.swift */; }; BF3338FD2E1626B000B10F76 /* BRPlayerControlProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338FC2E1626A500B10F76 /* BRPlayerControlProtocol.swift */; }; + BF3A56812E30C08F009E5CF9 /* BRHotSearchTagCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3A56802E30C08F009E5CF9 /* BRHotSearchTagCell.swift */; }; + BF3A56832E30C561009E5CF9 /* BRSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3A56822E30C561009E5CF9 /* BRSearchResultView.swift */; }; + BF3A56852E30CA78009E5CF9 /* BRSearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3A56842E30CA78009E5CF9 /* BRSearchResultCell.swift */; }; BF692AEB2E0A475D00A5C2DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF692AE12E0A475D00A5C2DA /* AppDelegate.swift */; }; BF692AEC2E0A475D00A5C2DA /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF692AE82E0A475D00A5C2DA /* SceneDelegate.swift */; }; BF692AEE2E0A475D00A5C2DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF692AE22E0A475D00A5C2DA /* Assets.xcassets */; }; @@ -171,6 +182,14 @@ BF02B8212E2FAB1600172177 /* BRAboutUsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRAboutUsViewController.swift; sourceTree = ""; }; BF02B8232E2FAEB500172177 /* BRAboutUsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRAboutUsCell.swift; sourceTree = ""; }; BF02B8252E2FB36A00172177 /* BRAboutUsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRAboutUsHeaderView.swift; sourceTree = ""; }; + BF02B8272E30821B00172177 /* BRSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchViewController.swift; sourceTree = ""; }; + BF02B82C2E30855300172177 /* BRSearchTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchTextView.swift; sourceTree = ""; }; + BF02B82E2E30895700172177 /* BRSearchRecordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchRecordView.swift; sourceTree = ""; }; + BF02B8302E30897700172177 /* BRSearchHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchHomeView.swift; sourceTree = ""; }; + BF02B8322E308E4300172177 /* BRSearchRecordTagCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchRecordTagCell.swift; sourceTree = ""; }; + BF02B8352E30ACEE00172177 /* BRSearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchViewModel.swift; sourceTree = ""; }; + BF02B8382E30B30400172177 /* AlignedCollectionViewFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlignedCollectionViewFlowLayout.swift; sourceTree = ""; }; + BF02B83A2E30BB4C00172177 /* BRHotSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRHotSearchView.swift; sourceTree = ""; }; BF0DBDD02E0D4E150035F6B4 /* BRTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRTabBar.swift; sourceTree = ""; }; BF3338E72E15218F00B10F76 /* UINavigationBar+BRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+BRAdd.swift"; sourceTree = ""; }; BF3338E92E152B8100B10F76 /* BRPlayerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRPlayerCache.swift; sourceTree = ""; }; @@ -182,6 +201,9 @@ BF3338F82E16178700B10F76 /* BRDetailControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRDetailControlView.swift; sourceTree = ""; }; BF3338FA2E161CF000B10F76 /* NSNumber+BRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNumber+BRAdd.swift"; sourceTree = ""; }; BF3338FC2E1626A500B10F76 /* BRPlayerControlProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRPlayerControlProtocol.swift; sourceTree = ""; }; + BF3A56802E30C08F009E5CF9 /* BRHotSearchTagCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRHotSearchTagCell.swift; sourceTree = ""; }; + BF3A56822E30C561009E5CF9 /* BRSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchResultView.swift; sourceTree = ""; }; + BF3A56842E30CA78009E5CF9 /* BRSearchResultCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRSearchResultCell.swift; sourceTree = ""; }; BF692AC92E0A475500A5C2DA /* BeeReel.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BeeReel.app; sourceTree = BUILT_PRODUCTS_DIR; }; BF692AE12E0A475D00A5C2DA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; BF692AE22E0A475D00A5C2DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -394,6 +416,55 @@ path = WebView; sourceTree = ""; }; + BF02B8292E30851900172177 /* Search */ = { + isa = PBXGroup; + children = ( + BF02B82A2E30852700172177 /* Controller */, + BF02B82B2E30852F00172177 /* View */, + BF02B8342E30ACCE00172177 /* ViewModel */, + ); + path = Search; + sourceTree = ""; + }; + BF02B82A2E30852700172177 /* Controller */ = { + isa = PBXGroup; + children = ( + BF02B8272E30821B00172177 /* BRSearchViewController.swift */, + ); + path = Controller; + sourceTree = ""; + }; + BF02B82B2E30852F00172177 /* View */ = { + isa = PBXGroup; + children = ( + BF3A56822E30C561009E5CF9 /* BRSearchResultView.swift */, + BF3A56842E30CA78009E5CF9 /* BRSearchResultCell.swift */, + BF02B8302E30897700172177 /* BRSearchHomeView.swift */, + BF02B82C2E30855300172177 /* BRSearchTextView.swift */, + BF02B82E2E30895700172177 /* BRSearchRecordView.swift */, + BF02B8322E308E4300172177 /* BRSearchRecordTagCell.swift */, + BF02B83A2E30BB4C00172177 /* BRHotSearchView.swift */, + BF3A56802E30C08F009E5CF9 /* BRHotSearchTagCell.swift */, + ); + path = View; + sourceTree = ""; + }; + BF02B8342E30ACCE00172177 /* ViewModel */ = { + isa = PBXGroup; + children = ( + BF02B8352E30ACEE00172177 /* BRSearchViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + BF02B8372E30B2F800172177 /* AlignedCollectionViewFlowLayout */ = { + isa = PBXGroup; + children = ( + BF02B8382E30B30400172177 /* AlignedCollectionViewFlowLayout.swift */, + ); + path = AlignedCollectionViewFlowLayout; + sourceTree = ""; + }; BF3338ED2E15566C00B10F76 /* Explore */ = { isa = PBXGroup; children = ( @@ -490,6 +561,7 @@ BF692AF52E0A47D400A5C2DA /* Class */ = { isa = PBXGroup; children = ( + BF02B8292E30851900172177 /* Search */, BF02B8102E2F83A100172177 /* Mine */, BF02B8052E2F612200172177 /* Favorites */, BF3338ED2E15566C00B10F76 /* Explore */, @@ -515,6 +587,7 @@ BF692AF72E0A480E00A5C2DA /* Thirdparty */ = { isa = PBXGroup; children = ( + BF02B8372E30B2F800172177 /* AlignedCollectionViewFlowLayout */, BFC676A62E12AF04006659E5 /* FlowLayout */, BFC676612E0E2C8E006659E5 /* WMZBanner */, BF692B292E0A84F700A5C2DA /* JXUUID */, @@ -1016,6 +1089,7 @@ BFC676732E0E938B006659E5 /* BRTableView.swift in Sources */, BFC676932E126A62006659E5 /* BRSpotlightNewMainCell.swift in Sources */, BFC6768D2E123D6E006659E5 /* AttributedString+BRAdd.swift in Sources */, + BF02B8392E30B30400172177 /* AlignedCollectionViewFlowLayout.swift in Sources */, BF3338F52E1616B200B10F76 /* BRExploreControlView.swift in Sources */, BF692B132E0A7B9000A5C2DA /* BRUserInfo.swift in Sources */, BF692B042E0A76D200A5C2DA /* BRLoginManager.swift in Sources */, @@ -1031,6 +1105,7 @@ BF02B7E12E2DE64200172177 /* BRVideoProgressView.swift in Sources */, BF692B542E0AA8FA00A5C2DA /* BRCollectionView.swift in Sources */, BF3338E82E15219500B10F76 /* UINavigationBar+BRAdd.swift in Sources */, + BF02B82D2E30855300172177 /* BRSearchTextView.swift in Sources */, BF692B472E0A9B7900A5C2DA /* BRPlayer.swift in Sources */, BF692B6E2E0BD4CB00A5C2DA /* BRHomeHeaderView.swift in Sources */, BF692AFA2E0A6F0900A5C2DA /* BRNetwork.swift in Sources */, @@ -1039,14 +1114,17 @@ BF692B7C2E0D3C1300A5C2DA /* BRVideoInfoModel.swift in Sources */, BFC6766D2E0E3A8D006659E5 /* BRImageView.swift in Sources */, BF02B7ED2E2E390500172177 /* BRScrollView.swift in Sources */, + BF3A56812E30C08F009E5CF9 /* BRHotSearchTagCell.swift in Sources */, BF02B8132E2F83C200172177 /* BRMineViewController.swift in Sources */, BF02B8222E2FAB1600172177 /* BRAboutUsViewController.swift in Sources */, BFC6766F2E0E3B5C006659E5 /* UIImageView+BRAdd.swift in Sources */, BF692B782E0D3A1200A5C2DA /* BRHomeModuleItem.swift in Sources */, BF692B5A2E0AAADD00A5C2DA /* BRPlayerListCell.swift in Sources */, + BF02B8312E30897700172177 /* BRSearchHomeView.swift in Sources */, BF692B162E0A7CD600A5C2DA /* BRHUD.swift in Sources */, BF3338F72E16176900B10F76 /* BRDetailPlayerCell.swift in Sources */, BF3338EA2E152B8100B10F76 /* BRPlayerCache.swift in Sources */, + BF3A56832E30C561009E5CF9 /* BRSearchResultView.swift in Sources */, BFC676952E126BBF006659E5 /* BRSpotlightNewCell.swift in Sources */, BF692B402E0A8FA100A5C2DA /* UIColor+BRAdd.swift in Sources */, BF692B102E0A7B4300A5C2DA /* BRUserDefaultsKey.swift in Sources */, @@ -1054,6 +1132,7 @@ BFC676912E126248006659E5 /* BRSpotlightTopCell.swift in Sources */, BFC676B72E137DFC006659E5 /* BRPopularPicksCell.swift in Sources */, BF692B422E0A8FB500A5C2DA /* UIFont+BRAdd.swift in Sources */, + BF02B8362E30ACEE00172177 /* BRSearchViewModel.swift in Sources */, BF692AEC2E0A475D00A5C2DA /* SceneDelegate.swift in Sources */, BF692B492E0A9D0E00A5C2DA /* UIView+BRAdd.swift in Sources */, BF02B7F82E2F211A00172177 /* BRHomeCategoriesMainCell.swift in Sources */, @@ -1078,6 +1157,8 @@ BF692B582E0AAA6F00A5C2DA /* UIScreen+BRAdd.swift in Sources */, BF692B1F2E0A804600A5C2DA /* BRLocalizedManager.swift in Sources */, BF02B7E92E2E29E900172177 /* BREpisodeSelectorCell.swift in Sources */, + BF02B83B2E30BB4C00172177 /* BRHotSearchView.swift in Sources */, + BF02B8332E308E4300172177 /* BRSearchRecordTagCell.swift in Sources */, BF692B612E0B814F00A5C2DA /* BRTabBarItemContentView.swift in Sources */, BF02B7F12E2E55E300172177 /* BRRateSelectorView.swift in Sources */, BF692B012E0A74A200A5C2DA /* BRDefine.swift in Sources */, @@ -1120,14 +1201,17 @@ BF692B562E0AA92100A5C2DA /* BRCollectionViewCell.swift in Sources */, BF02B7E32E2E08BD00172177 /* BRDetailEpButton.swift in Sources */, BF692B072E0A771C00A5C2DA /* BRModel.swift in Sources */, + BF02B82F2E30895700172177 /* BRSearchRecordView.swift in Sources */, BF692B752E0D39D000A5C2DA /* BRListModel.swift in Sources */, BF02B8172E2F881200172177 /* BRMineItem.swift in Sources */, BFC676B92E1385FC006659E5 /* BRPopularPicksSmallCell.swift in Sources */, BF692B512E0AA8C600A5C2DA /* BRPlayerListViewController.swift in Sources */, + BF02B8282E30821B00172177 /* BRSearchViewController.swift in Sources */, BFC676522E0D4EFD006659E5 /* BRHomeViewModel.swift in Sources */, BFC676A72E12AF04006659E5 /* WaterfallMutiSectionFlowLayout.swift in Sources */, BF692B2A2E0A84F700A5C2DA /* JXUUID.m in Sources */, BF692B2B2E0A84F700A5C2DA /* PDKeyChain.m in Sources */, + BF3A56852E30CA78009E5CF9 /* BRSearchResultCell.swift in Sources */, BF02B7FA2E2F225D00172177 /* BRHomeCategoryModel.swift in Sources */, BFC6766B2E0E395F006659E5 /* BRHomeHeaderBannerCell.swift in Sources */, ); diff --git a/BeeReel/Base/Controller/BRNavigationController.swift b/BeeReel/Base/Controller/BRNavigationController.swift index 09c44ad..eaed9ee 100644 --- a/BeeReel/Base/Controller/BRNavigationController.swift +++ b/BeeReel/Base/Controller/BRNavigationController.swift @@ -6,13 +6,14 @@ // import UIKit +import FDFullscreenPopGesture class BRNavigationController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() - - // Do any additional setup after loading the view. + fd_fullscreenPopGestureRecognizer.isEnabled = true + } override func pushViewController(_ viewController: UIViewController, animated: Bool) { diff --git a/BeeReel/Base/Extension/String+BRAdd.swift b/BeeReel/Base/Extension/String+BRAdd.swift index f645b3d..afb6155 100644 --- a/BeeReel/Base/Extension/String+BRAdd.swift +++ b/BeeReel/Base/Extension/String+BRAdd.swift @@ -10,3 +10,24 @@ import SmartCodable extension String: SmartCodable { } + +extension String { + + func br_range(of searchString: String) -> [NSRange] { + + do { + let newSearch = searchString.lowercased() + let newText = self.lowercased() + + let regex = try NSRegularExpression(pattern: newSearch) + let matches = regex.matches(in: newText, range: NSRange(self.startIndex..., in: newText)) + + let ranges = matches.map { $0.range } + + return ranges + } catch { + return [] + } + } + +} diff --git a/BeeReel/Base/Extension/UIColor+BRAdd.swift b/BeeReel/Base/Extension/UIColor+BRAdd.swift index 65ff369..a9a65c8 100644 --- a/BeeReel/Base/Extension/UIColor+BRAdd.swift +++ b/BeeReel/Base/Extension/UIColor+BRAdd.swift @@ -94,4 +94,32 @@ extension UIColor { static func colorB7B7B7(alpha: CGFloat = 1) -> UIColor { return UIColor(rgb: 0xB7B7B7, alpha: alpha) } + + static func colorF2F2F2(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xF2F2F2, alpha: alpha) + } + + static func colorFFF3F5(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xFFF3F5, alpha: alpha) + } + + static func colorE4FEFF(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xE4FEFF, alpha: alpha) + } + + static func colorFFFDF1(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xFFFDF1, alpha: alpha) + } + + static func colorFFD8DE(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xFFD8DE, alpha: alpha) + } + + static func color82F8FF(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0x82F8FF, alpha: alpha) + } + + static func colorFFEFA1(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xFFEFA1, alpha: alpha) + } } diff --git a/BeeReel/Base/Network/API/BRHomeAPI.swift b/BeeReel/Base/Network/API/BRHomeAPI.swift index 6554da9..884f7ca 100644 --- a/BeeReel/Base/Network/API/BRHomeAPI.swift +++ b/BeeReel/Base/Network/API/BRHomeAPI.swift @@ -81,4 +81,30 @@ class BRHomeAPI { completer?(response.data) } } + + ///热门搜索 + static func requestHotSearchList(completer: ((_ list: [BRShortModel]?) -> Void)?) { + var param = BRNetworkParameters(path: "/search/hots") + param.isLoding = false + param.isToast = false + param.method = .get + + BRNetwork.request(parameters: param) { (response: BRNetworkResponse>) in + completer?(response.data?.list) + } + } + + ///搜索 + static func requestSearch(text: String, completer: ((_ list: [BRShortModel]?) -> Void)?) { + var param = BRNetworkParameters(path: "/search") + param.method = .get + param.parameters = [ + "search" : text + ] + + BRNetwork.request(parameters: param) { (response: BRNetworkResponse>) in + completer?(response.data?.list) + } + + } } diff --git a/BeeReel/Base/Network/Base/BRNetworkTarget.swift b/BeeReel/Base/Network/Base/BRNetworkTarget.swift index fe48473..e2e694c 100644 --- a/BeeReel/Base/Network/Base/BRNetworkTarget.swift +++ b/BeeReel/Base/Network/Base/BRNetworkTarget.swift @@ -77,7 +77,8 @@ extension BRNetworkTarget: TargetType { "system-type" : "ios", "idfa" : JXUUID.idfa(), "model" : UIDevice.br_machineModelName(), - "authorization" : userToken + "authorization" : userToken, + "device-gaid" : JXUUID.idfv() ] return dic } diff --git a/BeeReel/Class/Explore/Controller/BRExploreViewController.swift b/BeeReel/Class/Explore/Controller/BRExploreViewController.swift index 0c6d9d3..e73debf 100644 --- a/BeeReel/Class/Explore/Controller/BRExploreViewController.swift +++ b/BeeReel/Class/Explore/Controller/BRExploreViewController.swift @@ -32,6 +32,7 @@ class BRExploreViewController: BRPlayerListViewController { private lazy var searchButton: UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "search_icon_01"), for: .normal) + button.addTarget(self, action: #selector(handleSearchButton), for: .touchUpInside) return button }() @@ -158,7 +159,6 @@ extension BRExploreViewController { BRVideoAPI.requestFavorite(isFavorite: isFavorite, shortPlayId: shortPlayId, videoId: shortModel?.short_play_video_id) { } - } @objc private func handleEpisodeButton() { @@ -167,6 +167,11 @@ extension BRExploreViewController { self.navigationController?.pushViewController(vc, animated: true) } + @objc private func handleSearchButton() { + let vc = BRSearchViewController() + self.navigationController?.pushViewController(vc, animated: true) + } + } diff --git a/BeeReel/Class/Favorites/Controller/BRFavoritesViewController.swift b/BeeReel/Class/Favorites/Controller/BRFavoritesViewController.swift index 8be1045..bd4c048 100644 --- a/BeeReel/Class/Favorites/Controller/BRFavoritesViewController.swift +++ b/BeeReel/Class/Favorites/Controller/BRFavoritesViewController.swift @@ -15,6 +15,12 @@ class BRFavoritesViewController: BRViewController { ///播放历史 private var playHistoryModel: BRShortModel? { didSet { + if let _ = playHistoryModel { + self.collectionViewLayout.headerReferenceSize = .init(width: UIScreen.width, height: 180) + } else { + self.collectionViewLayout.headerReferenceSize = .zero + } + collectionView.reloadData() } } @@ -33,7 +39,7 @@ class BRFavoritesViewController: BRViewController { layout.minimumInteritemSpacing = 11 layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15) layout.itemSize = .init(width: width, height: height) - layout.headerReferenceSize = .init(width: UIScreen.width, height: 180) + layout.headerReferenceSize = .zero return layout }() @@ -41,6 +47,12 @@ class BRFavoritesViewController: BRViewController { let collectionView = BRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) collectionView.delegate = self collectionView.dataSource = self + collectionView.br_addRefreshHeader { [weak self] in + self?.handleHeaderRefresh(nil) + } + collectionView.br_addRefreshBackFooter { [weak self] in + self?.handleFooterRefresh(nil) + } collectionView.register(BRFavoritesCell.self, forCellWithReuseIdentifier: "cell") collectionView.register(BRFavoritesHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "headerView") return collectionView @@ -71,7 +83,26 @@ class BRFavoritesViewController: BRViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.navigationController?.setNavigationBarHidden(true, animated: true) - + } + + override func viewDidAppear(_ animated: Bool) { + if self.hasViewDidAppear { + requestPlayHistorys() + } + super.viewDidAppear(animated) + } + + override func handleHeaderRefresh(_ completer: (() -> Void)?) { + self.requestPlayHistorys() + self.requestDataList(page: 1) { [weak self] in + self?.collectionView.br_endHeaderRefreshing() + } + } + + override func handleFooterRefresh(_ completer: (() -> Void)?) { + self.requestDataList(page: self.page + 1) { [weak self] in + self?.collectionView.br_endFooterRefreshing() + } } diff --git a/BeeReel/Class/Home/Controller/BRHomeViewController.swift b/BeeReel/Class/Home/Controller/BRHomeViewController.swift index 30ea909..82478df 100644 --- a/BeeReel/Class/Home/Controller/BRHomeViewController.swift +++ b/BeeReel/Class/Home/Controller/BRHomeViewController.swift @@ -133,6 +133,7 @@ class BRHomeViewController: BRViewController { private lazy var searchButton: UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "search_button_01"), for: .normal) + button.addTarget(self, action: #selector(handleSearchButton), for: .touchUpInside) return button }() @@ -237,6 +238,14 @@ extension BRHomeViewController { } +extension BRHomeViewController { + @objc private func handleSearchButton() { + let vc = BRSearchViewController() + + self.navigationController?.pushViewController(vc, animated: true) + } +} + extension BRHomeViewController { private func requestHomeData(completer: (() -> Void)? = nil) { diff --git a/BeeReel/Class/Home/Model/BRShortModel.swift b/BeeReel/Class/Home/Model/BRShortModel.swift index d456906..1248ec3 100644 --- a/BeeReel/Class/Home/Model/BRShortModel.swift +++ b/BeeReel/Class/Home/Model/BRShortModel.swift @@ -15,22 +15,6 @@ class BRShortModel: BRModel, SmartCodable { case r_720 = "720" case r_1080 = "1080" - var needLogin: Bool { - if self == .r_720 { - return true - } else { - return false - } - } - - var needVip: Bool { - if self == .r_1080 { - return true - } else { - return false - } - } - var toString: String { return "\(self.rawValue)P" } @@ -67,11 +51,6 @@ class BRShortModel: BRModel, SmartCodable { var revolution: VideoRevolution? - @IgnoredKey - var titleAttributedString: NSAttributedString? - @IgnoredKey - var br_isSelected: Bool? - static func mappingForKey() -> [SmartKeyTransformer]? { return [ diff --git a/BeeReel/Class/Mine/Controller/BRAboutUsViewController.swift b/BeeReel/Class/Mine/Controller/BRAboutUsViewController.swift index e050f99..fb28aad 100644 --- a/BeeReel/Class/Mine/Controller/BRAboutUsViewController.swift +++ b/BeeReel/Class/Mine/Controller/BRAboutUsViewController.swift @@ -33,7 +33,7 @@ class BRAboutUsViewController: BRViewController { }() private lazy var headerView: BRAboutUsHeaderView = { - let view = BRAboutUsHeaderView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 200)) + let view = BRAboutUsHeaderView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 250)) return view }() diff --git a/BeeReel/Class/Mine/Controller/BRMineViewController.swift b/BeeReel/Class/Mine/Controller/BRMineViewController.swift index 9bb974d..cacda6e 100644 --- a/BeeReel/Class/Mine/Controller/BRMineViewController.swift +++ b/BeeReel/Class/Mine/Controller/BRMineViewController.swift @@ -17,7 +17,7 @@ class BRMineViewController: BRViewController { ], [ BRMineItem(type: .web, icon: UIImage(named: "mine_item_icon_01"), title: "Privacy Policy".localized, url: kSBPrivacyPolicyWebUrl), - BRMineItem(type: .helpCenter, icon: UIImage(named: "mine_item_icon_02"), title: "Help Center".localized, url: ""), +// BRMineItem(type: .helpCenter, icon: UIImage(named: "mine_item_icon_02"), title: "Help Center".localized, url: ""), BRMineItem(type: .web, icon: UIImage(named: "mine_item_icon_03"), title: "User Agreement".localized, url: kSBUserAgreementWebUrl), BRMineItem(type: .aboutUs, icon: UIImage(named: "mine_item_icon_04"), title: "About Us".localized, url: ""), ] @@ -41,9 +41,14 @@ class BRMineViewController: BRViewController { return tableView }() + deinit { + NotificationCenter.default.removeObserver(self) + } + override func viewDidLoad() { super.viewDidLoad() self.edgesForExtendedLayout = [.top, .bottom] + NotificationCenter.default.addObserver(self, selector: #selector(userInfoUpdateNotification), name: BRLoginManager.userInfoUpdateNotification, object: nil) br_setupUI() } @@ -52,7 +57,11 @@ class BRMineViewController: BRViewController { super.viewWillAppear(animated) self.navigationController?.setNavigationBarHidden(true, animated: true) } - + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + BRLoginManager.manager.updateUserInfo(completer: nil) + } } @@ -77,6 +86,14 @@ extension BRMineViewController { } +extension BRMineViewController { + @objc private func userInfoUpdateNotification() { + self.tableView.reloadData() + } + + +} + //MARK: -------------- UITableViewDelegate UITableViewDataSource -------------- extension BRMineViewController: UITableViewDelegate, UITableViewDataSource { diff --git a/BeeReel/Class/Mine/View/BRAboutUsHeaderView.swift b/BeeReel/Class/Mine/View/BRAboutUsHeaderView.swift index b433dc4..7d766a7 100644 --- a/BeeReel/Class/Mine/View/BRAboutUsHeaderView.swift +++ b/BeeReel/Class/Mine/View/BRAboutUsHeaderView.swift @@ -9,12 +9,73 @@ import UIKit class BRAboutUsHeaderView: UIView { - /* - // Only override draw() if you perform custom drawing. - // An empty implementation adversely affects performance during animation. - override func draw(_ rect: CGRect) { - // Drawing code + + + private lazy var appLogoView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "logo_icon_01")) + imageView.layer.cornerRadius = 8 + imageView.layer.masksToBounds = true + imageView.isUserInteractionEnabled = true + let tap = UITapGestureRecognizer(target: self, action: #selector(handleLogoImageView)) + imageView.addGestureRecognizer(tap) + return imageView + }() + + private lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .fontBold(ofSize: 18) + label.textColor = .color1C1C1C() + label.text = kBRAPPName + return label + }() + + private lazy var versionLabel: UILabel = { + let label = UILabel() + label.font = .fontRegular(ofSize: 12) + label.textColor = .color1C1C1C(alpha: 0.8) + label.text = "version:\(kBRAPPVersion)" + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + br_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func handleLogoImageView() { + guard let url = URL(string: BRWebBaseURL) else { return } + UIApplication.shared.open(url) } - */ } + +extension BRAboutUsHeaderView { + + private func br_setupUI() { + addSubview(appLogoView) + addSubview(nameLabel) + addSubview(versionLabel) + + appLogoView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(50) + make.width.height.equalTo(100) + } + + nameLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(appLogoView.snp.bottom).offset(10) + } + + versionLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(nameLabel.snp.bottom).offset(5) + } + + } + +} diff --git a/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift b/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift index ec82819..df454ab 100644 --- a/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift +++ b/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift @@ -38,6 +38,7 @@ class BRVideoDetailViewController: BRPlayerListViewController { super.viewDidLoad() self.delegate = self self.dataSource = self + self.fd_interactivePopDisabled = true self.requestDetailData() diff --git a/BeeReel/Class/Search/Controller/BRSearchViewController.swift b/BeeReel/Class/Search/Controller/BRSearchViewController.swift new file mode 100644 index 0000000..95290b5 --- /dev/null +++ b/BeeReel/Class/Search/Controller/BRSearchViewController.swift @@ -0,0 +1,119 @@ +// +// BRSearchViewController.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/23. +// + +import UIKit + +class BRSearchViewController: BRViewController { + + private lazy var viewModel = BRSearchViewModel() + + private lazy var bgView: UIView = { + let view = UIImageView(image: UIImage(named: "搜索bg")) + return view + }() + + private lazy var backButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "nav_back_icon_02"), for: .normal) + button.addTarget(self, action: #selector(handleNavBack), for: .touchUpInside) + return button + }() + + private lazy var textView: BRSearchTextView = { + let view = BRSearchTextView() + view.didSearch = { [weak self] text in + guard let self = self else { return } + self.search(text: text) + } + return view + }() + + private lazy var homeView: BRSearchHomeView = { + let view = BRSearchHomeView() + view.searchText = { [weak self] text in + self?.search(text: text) + } + view.viewModel = self.viewModel + return view + }() + + private lazy var resultView: BRSearchResultView = { + let view = BRSearchResultView() + view.isHidden = true + return view + }() + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .colorFFFFFF() + br_setupUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + +} + +extension BRSearchViewController { + + private func search(text: String) { + self.viewModel.addSearchRecord(text: text) + self.resultView.search(text: text) + + if self.textView.text != text { + self.textView.text = text + } + if text.isEmpty { + self.homeView.isHidden = false + self.resultView.isHidden = true + } else { + self.homeView.isHidden = true + self.resultView.isHidden = false + } + } + +} + + +extension BRSearchViewController { + + private func br_setupUI() { + view.addSubview(bgView) + view.addSubview(backButton) + view.addSubview(textView) + view.addSubview(homeView) + view.addSubview(resultView) + + bgView.snp.makeConstraints { make in + make.left.top.equalToSuperview() + } + + backButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(15) + make.top.equalToSuperview().offset(UIScreen.statusBarHeight + 16) + } + + textView.snp.makeConstraints { make in + make.centerY.equalTo(backButton) + make.left.equalTo(backButton.snp.right).offset(10) + make.right.equalToSuperview().offset(-15) + } + + homeView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(textView.snp.bottom).offset(20) + } + + resultView.snp.makeConstraints { make in + make.edges.equalTo(homeView) + } + } + +} diff --git a/BeeReel/Class/Search/View/BRHotSearchTagCell.swift b/BeeReel/Class/Search/View/BRHotSearchTagCell.swift new file mode 100644 index 0000000..beb5f78 --- /dev/null +++ b/BeeReel/Class/Search/View/BRHotSearchTagCell.swift @@ -0,0 +1,67 @@ +// +// BRHotSearchTagCell.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/23. +// + +import UIKit + +class BRHotSearchTagCell: BRCollectionViewCell { + + var model: BRShortModel? { + didSet { + label.text = model?.name + } + } + var bgColor: UIColor? { + didSet { + self.contentView.backgroundColor = bgColor + } + } + + var borderColor: UIColor? { + didSet { + self.contentView.layer.borderColor = borderColor?.cgColor + } + } + + + private lazy var hotIconImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "hot_icon_04")) + return imageView + }() + + private lazy var label: UILabel = { + let label = UILabel() + label.font = .fontRegular(ofSize: 12) + label.textColor = .color000000() + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + self.contentView.layer.cornerRadius = 8 + self.contentView.layer.masksToBounds = true + self.contentView.layer.borderWidth = 1 + + contentView.addSubview(hotIconImageView) + contentView.addSubview(label) + + hotIconImageView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(9) + } + + label.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalTo(hotIconImageView.snp.right).offset(4) + make.right.equalToSuperview().offset(-10) + } + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/BeeReel/Class/Search/View/BRHotSearchView.swift b/BeeReel/Class/Search/View/BRHotSearchView.swift new file mode 100644 index 0000000..f4927b3 --- /dev/null +++ b/BeeReel/Class/Search/View/BRHotSearchView.swift @@ -0,0 +1,115 @@ +// +// BRHotSearchView.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/23. +// + +import UIKit + +class BRHotSearchView: UIView { + + var listArr: [BRShortModel] = [] { + didSet { + self.collectionView.reloadData() + } + } + + var tagBgColorArr: [UIColor] = [.colorFFF3F5(), .colorE4FEFF(), .colorFFFDF1()] + var tagBorderColorArr: [UIColor] = [.colorFFD8DE(), .color82F8FF(), .colorFFEFA1()] + + var searchText: ((_ text: String) -> Void)? + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .fontMedium(ofSize: 15) + label.textColor = .color1C1C1C() + label.text = "Most Searched".localized + return label + }() + + private lazy var collectionViewLayout: AlignedCollectionViewFlowLayout = { + let layout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .top) + layout.estimatedItemSize = .init(width: 30, height: 28) + layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15) + layout.minimumInteritemSpacing = 12 + layout.minimumLineSpacing = 10 + return layout + }() + + private lazy var collectionView: BRCollectionView = { + let collectionView = BRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.addObserver(self, forKeyPath: "contentSize", context: nil) + collectionView.register(BRHotSearchTagCell.self, forCellWithReuseIdentifier: "tagCell") + return collectionView + }() + + deinit { + self.collectionView.removeObserver(self, forKeyPath: "contentSize") + } + + override init(frame: CGRect) { + super.init(frame: frame) + br_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 == "contentSize" { + let height = self.collectionView.contentSize.height + self.collectionView.snp.updateConstraints { make in + make.height.equalTo(height + 1) + } + } + } + +} + +extension BRHotSearchView { + + private func br_setupUI() { + addSubview(titleLabel) + addSubview(collectionView) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(15) + make.top.equalToSuperview() + } + + collectionView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.right.equalToSuperview() + make.top.equalToSuperview().offset(31) + make.height.equalTo(1) + make.bottom.equalToSuperview() + } + } + +} + +extension BRHotSearchView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let colorIndex = indexPath.row % 3 + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "tagCell", for: indexPath) as! BRHotSearchTagCell + cell.model = listArr[indexPath.row] + cell.bgColor = tagBgColorArr[colorIndex] + cell.borderColor = tagBorderColorArr[colorIndex] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return listArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard let text = self.listArr[indexPath.row].name, !text.isEmpty else { return } + self.searchText?(text) + } + +} diff --git a/BeeReel/Class/Search/View/BRSearchHomeView.swift b/BeeReel/Class/Search/View/BRSearchHomeView.swift new file mode 100644 index 0000000..926ca5f --- /dev/null +++ b/BeeReel/Class/Search/View/BRSearchHomeView.swift @@ -0,0 +1,123 @@ +// +// BRSearchHomeView.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/23. +// + +import UIKit + +class BRSearchHomeView: UIView { + + weak var viewModel: BRSearchViewModel? { + didSet { + recordView.listArr = viewModel?.recordList ?? [] + viewModel?.addObserver(self, forKeyPath: "recordList", context: nil) + updateLayout() + } + } + + var searchText: ((_ text: String) -> Void)? + + private lazy var scrollView: BRScrollView = { + let scrollView = BRScrollView() + return scrollView + }() + + private lazy var stackView: UIStackView = { + let view = UIStackView() + view.axis = .vertical + view.spacing = 30 + return view + }() + + private lazy var recordView: BRSearchRecordView = { + let view = BRSearchRecordView() + view.clickCleanButton = { [weak self] in + self?.viewModel?.clearSearchRecord() + } + view.searchText = { [weak self] text in + self?.searchText?(text) + } + return view + }() + + private lazy var hotView: BRHotSearchView = { + let view = BRHotSearchView() + view.searchText = { [weak self] text in + self?.searchText?(text) + } + return view + }() + + deinit { + self.viewModel?.removeObserver(self, forKeyPath: "recordList") + } + + override init(frame: CGRect) { + super.init(frame: frame) + + br_setupUI() + + requestHotSearch() + } + + 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 == "recordList" { + recordView.listArr = viewModel?.recordList ?? [] + updateLayout() + } + } + + private func updateLayout() { + stackView.br_removeAllArrangedSubview() + + if self.viewModel?.recordList.isEmpty == false { + stackView.addArrangedSubview(recordView) + } + + if self.viewModel?.hotSearchList.isEmpty == false { + stackView.addArrangedSubview(hotView) + } + } + +} + +extension BRSearchHomeView { + + private func br_setupUI() { + addSubview(scrollView) + scrollView.addSubview(stackView) + + scrollView.snp.makeConstraints { make in + make.left.right.top.equalToSuperview() + make.bottom.equalToSuperview() + } + + stackView.snp.makeConstraints { make in + make.left.top.equalToSuperview() + make.width.equalTo(UIScreen.width) + make.bottom.equalToSuperview().offset(-(UIScreen.tabbarSafeBottomMargin + 10)) + } + } + +} + +extension BRSearchHomeView { + + private func requestHotSearch() { + + BRHomeAPI.requestHotSearchList { [weak self] list in + guard let self = self else { return } + guard let list = list else { return } + self.viewModel?.hotSearchList = list + self.hotView.listArr = list + self.updateLayout() + } + } + +} diff --git a/BeeReel/Class/Search/View/BRSearchRecordTagCell.swift b/BeeReel/Class/Search/View/BRSearchRecordTagCell.swift new file mode 100644 index 0000000..8e99395 --- /dev/null +++ b/BeeReel/Class/Search/View/BRSearchRecordTagCell.swift @@ -0,0 +1,45 @@ +// +// BRSearchRecordTagCell.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/23. +// + +import UIKit + +class BRSearchRecordTagCell: BRCollectionViewCell { + + var text: String? { + didSet { + label.text = text + } + } + + private lazy var label: UILabel = { + let label = UILabel() + label.font = .fontRegular(ofSize: 12) + label.textColor = .color1C1C1C() + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + contentView.layer.cornerRadius = 8 + contentView.layer.masksToBounds = true + contentView.backgroundColor = .colorF2F2F2() + + contentView.addSubview(label) + + label.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(10) + make.centerX.equalToSuperview() + } + + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/BeeReel/Class/Search/View/BRSearchRecordView.swift b/BeeReel/Class/Search/View/BRSearchRecordView.swift new file mode 100644 index 0000000..e31004c --- /dev/null +++ b/BeeReel/Class/Search/View/BRSearchRecordView.swift @@ -0,0 +1,130 @@ +// +// BRSearchRecordView.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/23. +// + +import UIKit + +class BRSearchRecordView: UIView { + + var listArr: [String] = [] { + didSet { + collectionView.reloadData() + } + } + + var clickCleanButton: (() -> Void)? + var searchText: ((_ text: String) -> Void)? + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .fontMedium(ofSize: 13) + label.textColor = .color777777() + label.text = "Last Searched".localized + return label + }() + + private lazy var collectionViewLayout: AlignedCollectionViewFlowLayout = { + let layout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .top) + layout.estimatedItemSize = .init(width: 30, height: 28) + layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 0) + layout.minimumInteritemSpacing = 10 + layout.minimumLineSpacing = 10 + return layout + }() + + private lazy var collectionView: BRCollectionView = { + let collectionView = BRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.addObserver(self, forKeyPath: "contentSize", context: nil) + collectionView.register(BRSearchRecordTagCell.self, forCellWithReuseIdentifier: "tagCell") + return collectionView + }() + + private lazy var deleteButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "delete_icon_01"), for: .normal) + button.setContentHuggingPriority(.required, for: .horizontal) + button.setContentCompressionResistancePriority(.required, for: .horizontal) + button.addTarget(self, action: #selector(handleDeleteButton), for: .touchUpInside) + return button + }() + + deinit { + self.collectionView.removeObserver(self, forKeyPath: "contentSize") + } + + override init(frame: CGRect) { + super.init(frame: frame) + + br_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func handleDeleteButton() { + self.clickCleanButton?() + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "contentSize" { + let height = self.collectionView.contentSize.height + self.collectionView.snp.updateConstraints { make in + make.height.equalTo(height + 1) + } + } + } +} + +extension BRSearchRecordView { + + private func br_setupUI() { + addSubview(titleLabel) + addSubview(collectionView) + addSubview(deleteButton) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(15) + make.top.equalToSuperview() + } + + collectionView.snp.makeConstraints { make in + make.left.equalToSuperview() + make.top.equalToSuperview().offset(28) + make.right.equalTo(deleteButton.snp.left).offset(-15) + make.height.equalTo(1) + make.bottom.equalToSuperview() + } + + deleteButton.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-15) + make.top.equalToSuperview().offset(33) + } + + + } + +} + +//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource -------------- +extension BRSearchRecordView: UICollectionViewDelegate, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "tagCell", for: indexPath) as! BRSearchRecordTagCell + cell.text = listArr[indexPath.row] + return cell + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return listArr.count + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + self.searchText?(listArr[indexPath.row]) + } +} diff --git a/BeeReel/Class/Search/View/BRSearchResultCell.swift b/BeeReel/Class/Search/View/BRSearchResultCell.swift new file mode 100644 index 0000000..799306c --- /dev/null +++ b/BeeReel/Class/Search/View/BRSearchResultCell.swift @@ -0,0 +1,145 @@ +// +// BRSearchResultCell.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/23. +// + +import UIKit + +class BRSearchResultCell: BRCollectionViewCell { + + var highlightText: String? + + var model: BRShortModel? { + didSet { + coverView.br_setImage(url: model?.image_url) + descLabel.text = model?.br_description + hotView.setNeedsUpdateConfiguration() + categoryLabel.text = model?.category?.first + + + let name = model?.name ?? "" + let ranges = name.br_range(of: highlightText ?? "") + let nameString = NSMutableAttributedString(string: name) + + ranges.forEach { + nameString.yy_setColor(.colorFF7489(), range: $0) + } + + nameLabel.attributedText = nameString + + } + } + + private lazy var bgView: UIView = { + let view = UIImageView(image: UIImage(named: "Vector")) + return view + }() + + private lazy var coverView: BRImageView = { + let view = BRImageView() + view.layer.cornerRadius = 8 + view.layer.masksToBounds = true + return view + }() + + private lazy var nameLabel: UILabel = { + let label = UILabel() + label.font = .fontMedium(ofSize: 14) + label.textColor = .color1C1C1C() + return label + }() + + private lazy var categoryLabel: UILabel = { + let label = UILabel() + label.font = .fontRegular(ofSize: 10) + label.textColor = .color899D00() + return label + }() + + private lazy var descLabel: UILabel = { + let label = UILabel() + label.font = .fontRegular(ofSize: 10) + label.textColor = .color777777() + label.numberOfLines = 2 + return label + }() + + private lazy var hotView: UIButton = { + var config = UIButton.Configuration.plain() + config.image = UIImage(named: "hot_icon_02") + config.imagePadding = 2 + config.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0) + + let button = UIButton(configuration: config) + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + let count = model?.watch_total ?? 0 + var str = "\(count)" + if count > 1000 { + let num = NSNumber(value: Float(count) / 1000) + str = num.br_toString(maximumFractionDigits: 1) + "k" + } + button.configuration?.attributedTitle = AttributedString.br_createAttributedString(string: str, color: .colorFF7489(), font: .fontRegular(ofSize: 10)) + + } + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + br_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +extension BRSearchResultCell { + + private func br_setupUI() { + contentView.addSubview(bgView) + bgView.addSubview(coverView) + bgView.addSubview(nameLabel) + bgView.addSubview(categoryLabel) + bgView.addSubview(descLabel) + bgView.addSubview(hotView) + + bgView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + coverView.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.equalToSuperview().offset(7) + make.width.equalTo(90) + make.height.equalTo(112) + } + + nameLabel.snp.makeConstraints { make in + make.left.equalTo(coverView.snp.right).offset(20) + make.top.equalToSuperview().offset(16) + make.right.lessThanOrEqualToSuperview().offset(-10) + } + + categoryLabel.snp.makeConstraints { make in + make.left.equalTo(nameLabel) + make.top.equalToSuperview().offset(41) + } + + descLabel.snp.makeConstraints { make in + make.left.equalTo(nameLabel) + make.top.equalToSuperview().offset(55) + make.right.lessThanOrEqualToSuperview().offset(-10) + } + + hotView.snp.makeConstraints { make in + make.left.equalTo(nameLabel) + make.bottom.equalToSuperview().offset(-16) + } + } + +} diff --git a/BeeReel/Class/Search/View/BRSearchResultView.swift b/BeeReel/Class/Search/View/BRSearchResultView.swift new file mode 100644 index 0000000..b9094a5 --- /dev/null +++ b/BeeReel/Class/Search/View/BRSearchResultView.swift @@ -0,0 +1,112 @@ +// +// BRSearchResultView.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/23. +// + +import UIKit + +class BRSearchResultView: UIView { + + private var searchText: String = "" + + private var listArr: [BRShortModel] = [] + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.textColor = .color1C1C1C() + label.font = .fontMedium(ofSize: 15) + label.text = "Search Results".localized + return label + }() + + private lazy var collectionViewLayout: UICollectionViewFlowLayout = { + let layout = UICollectionViewFlowLayout() + layout.itemSize = .init(width: UIScreen.width - 30, height: 126) + layout.minimumLineSpacing = 10 + layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15) + return layout + }() + + private lazy var collectionView: BRCollectionView = { + let collectionView = BRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.tabbarSafeBottomMargin + 10, right: 0) + collectionView.register(BRSearchResultCell.self, forCellWithReuseIdentifier: "cell") + return collectionView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + br_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + func search(text: String) { + self.searchText = text + BRAppTool.keyWindow?.endEditing(true) + + if text.isEmpty { + self.listArr.removeAll() + self.collectionView.reloadData() + } else { + requestSearch(text: text) + } + } +} + +extension BRSearchResultView { + + private func br_setupUI() { + addSubview(titleLabel) + addSubview(collectionView) + + titleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(15) + make.top.equalToSuperview() + } + + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalTo(titleLabel.snp.bottom).offset(10) + } + } + +} + +extension BRSearchResultView: UICollectionViewDelegate, UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! BRSearchResultCell + cell.highlightText = self.searchText + cell.model = self.listArr[indexPath.row] + return cell + } + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + self.listArr.count + } +} + + +extension BRSearchResultView { + + private func requestSearch(text: String) { + + + BRHomeAPI.requestSearch(text: searchText) { [weak self] list in + guard let self = self else { return } + guard text == self.searchText else { return } + + self.listArr = list ?? [] + + self.collectionView.reloadData() + + } + } + +} diff --git a/BeeReel/Class/Search/View/BRSearchTextView.swift b/BeeReel/Class/Search/View/BRSearchTextView.swift new file mode 100644 index 0000000..8bbfbd9 --- /dev/null +++ b/BeeReel/Class/Search/View/BRSearchTextView.swift @@ -0,0 +1,76 @@ +// +// BRSearchTextView.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/23. +// + +import UIKit + +class BRSearchTextView: UIView { + + override var intrinsicContentSize: CGSize { + return .init(width: UIScreen.width, height: 40) + } + + + var didSearch: ((_ text: String) -> Void)? + + + var text: String? { + get { + return textField.text + } + set { + textField.text = newValue + } + } + + private lazy var textField: UITextField = { + let placeholder = NSMutableAttributedString(string: "Hot Picks Waiting for You".localized) + placeholder.yy_font = .fontRegular(ofSize: 14) + placeholder.yy_color = .colorD3D3D3() + + let textField = UITextField() + textField.attributedPlaceholder = placeholder + textField.font = .fontRegular(ofSize: 14) + textField.textColor = .color1C1C1C() + textField.returnKeyType = .search + textField.delegate = self + return textField + }() + + override init(frame: CGRect) { + super.init(frame: frame) + self.layer.cornerRadius = 20 + self.layer.masksToBounds = true + self.layer.borderColor = UIColor.color1C1C1C().cgColor + self.layer.borderWidth = 1 + self.backgroundColor = .colorFFFFFF() + + self.addSubview(textField) + + textField.snp.makeConstraints { make in + make.left.equalToSuperview().offset(20) + make.right.equalToSuperview().offset(-20) + make.top.bottom.equalToSuperview() + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} + +//MARK: -------------- UITextFieldDelegate -------------- +extension BRSearchTextView: UITextFieldDelegate { + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + if let text = textField.text { + self.didSearch?(text) + } + return true + } +} diff --git a/BeeReel/Class/Search/ViewModel/BRSearchViewModel.swift b/BeeReel/Class/Search/ViewModel/BRSearchViewModel.swift new file mode 100644 index 0000000..e775281 --- /dev/null +++ b/BeeReel/Class/Search/ViewModel/BRSearchViewModel.swift @@ -0,0 +1,44 @@ +// +// BRSearchViewModel.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/23. +// + +import UIKit + +class BRSearchViewModel: NSObject { + static let searchRecordUserDefaultKey = "BRSearchViewModel.searchRecordUserDefaultKey" + + ///历史记录 + @objc dynamic private(set) var recordList: [String] = (UserDefaults.standard.object(forKey: BRSearchViewModel.searchRecordUserDefaultKey) as? [String]) ?? [] + + var hotSearchList: [BRShortModel] = [] + + func addSearchRecord(text: String) { + guard !text.isEmpty else { return } + var list = recordList + + for (index, value) in list.enumerated() { + if value == text { + list.remove(at: index) + break + } + } + + list.insert(text, at: 0) + + if list.count > 10 { + list.removeLast() + } + recordList = list + + UserDefaults.standard.set(list, forKey: BRSearchViewModel.searchRecordUserDefaultKey) + } + + func clearSearchRecord() { + recordList.removeAll() + UserDefaults.standard.set(recordList, forKey: BRSearchViewModel.searchRecordUserDefaultKey) + } + +} diff --git a/BeeReel/Delegate/AppDelegate+BRConfig.swift b/BeeReel/Delegate/AppDelegate+BRConfig.swift index 6452bf1..4d1f7d7 100644 --- a/BeeReel/Delegate/AppDelegate+BRConfig.swift +++ b/BeeReel/Delegate/AppDelegate+BRConfig.swift @@ -14,6 +14,8 @@ extension AppDelegate { //设置刷新控件的语言 MJRefreshConfig.default.languageCode = BRLocalizedManager.manager.mjLocalizedKey + + } } diff --git a/BeeReel/Lib/Login/BRLoginManager.swift b/BeeReel/Lib/Login/BRLoginManager.swift index e7963a2..646ea2f 100644 --- a/BeeReel/Lib/Login/BRLoginManager.swift +++ b/BeeReel/Lib/Login/BRLoginManager.swift @@ -33,70 +33,6 @@ class BRLoginManager { UserDefaults.br_setObject(token, forKey: kBRLoginTokenDefaultsKey) } - func openLogin(finishHandle: (() -> Void)? = nil) { -// let view = VPLoginContentView() -// view.loginFinishBlock = finishHandle -// view.present(in: nil) - } - - func login(type: LoginType, presentingViewController: UIViewController?, completer: ((_ isFinish: Bool) -> Void)?) { - -// switch type { -// case .apple: -// appleSignLogin { [weak self] model in -// self?.requestThirdLogin(thirdSignModel: model, completer: completer) -// } -// -// case .faceBook: -// facebookLogin(presentingViewController: presentingViewController) { [weak self] model in -// self?.requestThirdLogin(thirdSignModel: model, completer: completer) -// } -// default: -// completer?(false) -// } - - } - - ///退出登录 - func logout(completer: ((_ isFinish: Bool) -> Void)?) { -// VPStatAPI.requestLeaveApp() -// VPUserAPI.requestLogout { [weak self] token in -// guard let self = self else { return } -// if let token = token { -// self.setLoginToken(token: token) -// self.userInfo?.is_tourist = true -// self.updateUserInfo(completer: nil) -// VPStatAPI.requestStatOnLine() -// VPStatAPI.requestEnterApp() -// completer?(true) -// NotificationCenter.default.post(name: VPLoginManager.userInfoUpdateNotification, object: nil) -// NotificationCenter.default.post(name: VPLoginManager.loginStateDidChangeNotification, object: nil) -// } else { -// completer?(false) -// } -// } - } - - ///删除账号 - func deleteAccount(completer: ((_ isFinish: Bool) -> Void)?) { -// VPStatAPI.requestLeaveApp() -// VPUserAPI.requestDelete { [weak self] isFinish in -// guard let self = self else { return } -// if isFinish { -// self.setLoginToken(token: nil) -// self.userInfo?.is_tourist = true -// self.updateUserInfo(completer: nil) -// VPStatAPI.requestStatOnLine() -// VPStatAPI.requestEnterApp() -// completer?(true) -// NotificationCenter.default.post(name: VPLoginManager.userInfoUpdateNotification, object: nil) -// NotificationCenter.default.post(name: VPLoginManager.loginStateDidChangeNotification, object: nil) -// } else { -// completer?(false) -// } -// } - } - ///更新用户信息 func updateUserInfo(completer: (() -> Void)?) { @@ -119,8 +55,6 @@ class BRLoginManager { extension BRLoginManager { - ///登录状态发生变化 - @objc static let loginStateDidChangeNotification = NSNotification.Name(rawValue: "BRLoginManager.loginStateDidChangeNotification") ///用户信息更新 @objc static let userInfoUpdateNotification = NSNotification.Name(rawValue: "BRLoginManager.userInfoUpdateNotification") diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/Contents.json b/BeeReel/Sources/Assets.xcassets/LaunchScreen/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/LaunchScreen/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/Contents.json new file mode 100644 index 0000000..36577cc --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/bg@2x.png b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/bg@2x.png new file mode 100644 index 0000000..e129e97 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/bg@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/bg@3x.png b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/bg@3x.png new file mode 100644 index 0000000..7f5bf40 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_bg_image.imageset/bg@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_logo.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_logo.imageset/Contents.json new file mode 100644 index 0000000..71810bb --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_logo.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_logo.imageset/logo@2x.png b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_logo.imageset/logo@2x.png new file mode 100644 index 0000000..34e7670 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_logo.imageset/logo@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_logo.imageset/logo@3x.png b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_logo.imageset/logo@3x.png new file mode 100644 index 0000000..68f3d07 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_logo.imageset/logo@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_mark.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_mark.imageset/Contents.json new file mode 100644 index 0000000..887bad6 --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_mark.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "装饰@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "装饰@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_mark.imageset/装饰@2x.png b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_mark.imageset/装饰@2x.png new file mode 100644 index 0000000..6634c69 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_mark.imageset/装饰@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_mark.imageset/装饰@3x.png b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_mark.imageset/装饰@3x.png new file mode 100644 index 0000000..481c570 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_mark.imageset/装饰@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_text.imageset/Binge in Minutes, Love for Hours@2x.png b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_text.imageset/Binge in Minutes, Love for Hours@2x.png new file mode 100644 index 0000000..a2a1e44 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_text.imageset/Binge in Minutes, Love for Hours@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_text.imageset/Binge in Minutes, Love for Hours@3x.png b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_text.imageset/Binge in Minutes, Love for Hours@3x.png new file mode 100644 index 0000000..549b20c Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_text.imageset/Binge in Minutes, Love for Hours@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_text.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_text.imageset/Contents.json new file mode 100644 index 0000000..bee239e --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/LaunchScreen/launch_screen_text.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Binge in Minutes, Love for Hours@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Binge in Minutes, Love for Hours@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/Vector.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/Vector.imageset/Contents.json new file mode 100644 index 0000000..8690b19 --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/Vector.imageset/Contents.json @@ -0,0 +1,44 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Vector@2x.png", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "left" : 265, + "right" : 23 + }, + "center" : { + "mode" : "tile", + "width" : 1 + }, + "mode" : "3-part-horizontal" + }, + "scale" : "2x" + }, + { + "filename" : "Vector@3x.png", + "idiom" : "universal", + "resizing" : { + "cap-insets" : { + "left" : 398, + "right" : 34 + }, + "center" : { + "mode" : "tile", + "width" : 1 + }, + "mode" : "3-part-horizontal" + }, + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/Vector.imageset/Vector@2x.png b/BeeReel/Sources/Assets.xcassets/icon/Vector.imageset/Vector@2x.png new file mode 100644 index 0000000..7c208a3 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/Vector.imageset/Vector@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/Vector.imageset/Vector@3x.png b/BeeReel/Sources/Assets.xcassets/icon/Vector.imageset/Vector@3x.png new file mode 100644 index 0000000..658e181 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/Vector.imageset/Vector@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/delete_icon_01.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/delete_icon_01.imageset/Contents.json new file mode 100644 index 0000000..5c4d3b1 --- /dev/null +++ b/BeeReel/Sources/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/BeeReel/Sources/Assets.xcassets/icon/delete_icon_01.imageset/Frame@2x.png b/BeeReel/Sources/Assets.xcassets/icon/delete_icon_01.imageset/Frame@2x.png new file mode 100644 index 0000000..1df22e5 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/delete_icon_01.imageset/Frame@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/delete_icon_01.imageset/Frame@3x.png b/BeeReel/Sources/Assets.xcassets/icon/delete_icon_01.imageset/Frame@3x.png new file mode 100644 index 0000000..c0caad8 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/delete_icon_01.imageset/Frame@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/hot_icon_04.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/hot_icon_04.imageset/Contents.json new file mode 100644 index 0000000..5472a49 --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/hot_icon_04.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "hot@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "hot@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/hot_icon_04.imageset/hot@2x.png b/BeeReel/Sources/Assets.xcassets/icon/hot_icon_04.imageset/hot@2x.png new file mode 100644 index 0000000..5ff0eec Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/hot_icon_04.imageset/hot@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/hot_icon_04.imageset/hot@3x.png b/BeeReel/Sources/Assets.xcassets/icon/hot_icon_04.imageset/hot@3x.png new file mode 100644 index 0000000..6a4fbfa Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/hot_icon_04.imageset/hot@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/logo_icon_01.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/logo_icon_01.imageset/Contents.json new file mode 100644 index 0000000..71810bb --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/logo_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "logo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "logo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/logo_icon_01.imageset/logo@2x.png b/BeeReel/Sources/Assets.xcassets/icon/logo_icon_01.imageset/logo@2x.png new file mode 100644 index 0000000..34e7670 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/logo_icon_01.imageset/logo@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/logo_icon_01.imageset/logo@3x.png b/BeeReel/Sources/Assets.xcassets/icon/logo_icon_01.imageset/logo@3x.png new file mode 100644 index 0000000..68f3d07 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/logo_icon_01.imageset/logo@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/搜索bg.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/搜索bg.imageset/Contents.json new file mode 100644 index 0000000..1ffa57b --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/搜索bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "搜索bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "搜索bg.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/搜索bg.imageset/搜索bg.png b/BeeReel/Sources/Assets.xcassets/icon/搜索bg.imageset/搜索bg.png new file mode 100644 index 0000000..388f529 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/搜索bg.imageset/搜索bg.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/搜索bg.imageset/搜索bg@2x.png b/BeeReel/Sources/Assets.xcassets/icon/搜索bg.imageset/搜索bg@2x.png new file mode 100644 index 0000000..34a60e6 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/搜索bg.imageset/搜索bg@2x.png differ diff --git a/BeeReel/Sources/Base.lproj/LaunchScreen.storyboard b/BeeReel/Sources/Base.lproj/LaunchScreen.storyboard index 865e932..204e169 100644 --- a/BeeReel/Sources/Base.lproj/LaunchScreen.storyboard +++ b/BeeReel/Sources/Base.lproj/LaunchScreen.storyboard @@ -1,7 +1,9 @@ - - + + + - + + @@ -11,10 +13,35 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -22,4 +49,10 @@ + + + + + + diff --git a/BeeReel/Sources/Bridging-Header.h b/BeeReel/Sources/Bridging-Header.h index 4e44453..7d656f0 100644 --- a/BeeReel/Sources/Bridging-Header.h +++ b/BeeReel/Sources/Bridging-Header.h @@ -6,3 +6,4 @@ #import #import #import "WMZBannerView.h" +#import diff --git a/BeeReel/Sources/Localizable.xcstrings b/BeeReel/Sources/Localizable.xcstrings index 8cef08a..3ec5cbd 100644 --- a/BeeReel/Sources/Localizable.xcstrings +++ b/BeeReel/Sources/Localizable.xcstrings @@ -111,6 +111,39 @@ } } }, + "Hot Picks Waiting for You" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hot Picks Waiting for You" + } + } + } + }, + "Last Searched" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Last Searched" + } + } + } + }, + "Most Searched" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Most Searched" + } + } + } + }, "New Releases" : { "extractionState" : "manual", "localizations" : { @@ -144,6 +177,17 @@ } } }, + "Search Results" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Search Results" + } + } + } + }, "Speed" : { "extractionState" : "manual", "localizations" : { @@ -167,7 +211,15 @@ } }, "Third-party information sharing list" : { - "extractionState" : "manual" + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Third-party information sharing list" + } + } + } }, "Top 10" : { "extractionState" : "manual", diff --git a/BeeReel/Thirdparty/AlignedCollectionViewFlowLayout/AlignedCollectionViewFlowLayout.swift b/BeeReel/Thirdparty/AlignedCollectionViewFlowLayout/AlignedCollectionViewFlowLayout.swift new file mode 100644 index 0000000..6589dca --- /dev/null +++ b/BeeReel/Thirdparty/AlignedCollectionViewFlowLayout/AlignedCollectionViewFlowLayout.swift @@ -0,0 +1,432 @@ +// +// AlignedCollectionViewFlowLayout.swift +// +// Created by Mischa Hildebrand on 12/04/2017. +// Copyright © 2017 Mischa Hildebrand. +// +// Licensed under the terms of the MIT license: +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// MARK: - 🦆 Type definitions + +/// An abstract protocol that defines an alignment. +protocol Alignment {} + +/// Defines an alignment for UI elements. +public enum HorizontalAlignment: Alignment { + case left + case justified + case right +} + +/// Defines a vertical alignment for UI elements. +public enum VerticalAlignment: Alignment { + case top + case center + case bottom +} + +/// Describes an axis with respect to which items can be aligned. +private struct AlignmentAxis { + + /// Determines how items are aligned relative to the axis. + let alignment: A + + /// Defines the position of the axis. + /// * If the `Alignment` is horizontal, the alignment axis is vertical and this is the position on the `x` axis. + /// * If the `Alignment` is vertical, the alignment axis is horizontal and this is the position on the `y` axis. + let position: CGFloat +} + + +/// A `UICollectionViewFlowLayout` subclass that gives you control +/// over the horizontal and vertical alignment of the cells. +/// You can use it to align the cells like words in a left- or right-aligned text +/// and you can specify how the cells are vertically aligned in their row. +open class AlignedCollectionViewFlowLayout: UICollectionViewFlowLayout { + + // MARK: - 🔶 Properties + + /// Determines how the cells are horizontally aligned in a row. + /// - Note: The default is `.justified`. + public var horizontalAlignment: HorizontalAlignment = .justified + + /// Determines how the cells are vertically aligned in a row. + /// - Note: The default is `.center`. + public var verticalAlignment: VerticalAlignment = .center + + /// The vertical axis with respect to which the cells are horizontally aligned. + /// For a `justified` alignment the alignment axis is not defined and this value is `nil`. + fileprivate var alignmentAxis: AlignmentAxis? { + switch horizontalAlignment { + case .left: + return AlignmentAxis(alignment: HorizontalAlignment.left, position: sectionInset.left) + case .right: + guard let collectionViewWidth = collectionView?.frame.size.width else { + return nil + } + return AlignmentAxis(alignment: HorizontalAlignment.right, position: collectionViewWidth - sectionInset.right) + default: + return nil + } + } + + /// The width of the area inside the collection view that can be filled with cells. + private var contentWidth: CGFloat? { + guard let collectionViewWidth = collectionView?.frame.size.width else { + return nil + } + return collectionViewWidth - sectionInset.left - sectionInset.right + } + + + // MARK: - 👶 Initialization + + /// The designated initializer. + /// + /// - Parameters: + /// - horizontalAlignment: Specifies how the cells are horizontally aligned in a row. -- + /// (Default: `.justified`) + /// - verticalAlignment: Specified how the cells are vertically aligned in a row. -- + /// (Default: `.center`) + public init(horizontalAlignment: HorizontalAlignment = .justified, verticalAlignment: VerticalAlignment = .center) { + super.init() + self.horizontalAlignment = horizontalAlignment + self.verticalAlignment = verticalAlignment + } + + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + + // MARK: - 🅾️ Overrides + + override open func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + + // 💡 IDEA: + // The approach for computing a cell's frame is to create a rectangle that covers the current line. + // Then we check if the preceding cell's frame intersects with this rectangle. + // If it does, the current item is not the first item in the line. Otherwise it is. + // (Vice-versa for right-aligned cells.) + // + // +---------+----------------------------------------------------------------+---------+ + // | | | | + // | | +------------+ | | + // | | | | | | + // | section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section | + // | inset | |intersection| | | line rect | inset | + // | |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| | + // | (left) | | | current item | (right) | + // | | +------------+ | | + // | | previous item | | + // +---------+----------------------------------------------------------------+---------+ + // + // ℹ️ We need this rather complicated approach because the first item in a line + // is not always left-aligned and the last item in a line is not always right-aligned: + // If there is only one item in a line UICollectionViewFlowLayout will center it. + + // We may not change the original layout attributes or UICollectionViewFlowLayout might complain. + guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else { + return nil + } + + // For a justified layout there's nothing to do here + // as UICollectionViewFlowLayout justifies the items in a line by default. + if horizontalAlignment != .justified { + layoutAttributes.alignHorizontally(collectionViewLayout: self) + } + + // For a vertically centered layout there's nothing to do here + // as UICollectionViewFlowLayout center-aligns the items in a line by default. + if verticalAlignment != .center { + layoutAttributes.alignVertically(collectionViewLayout: self) + } + + return layoutAttributes + } + + override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + // We may not change the original layout attributes or UICollectionViewFlowLayout might complain. + let layoutAttributesObjects = copy(super.layoutAttributesForElements(in: rect)) + layoutAttributesObjects?.forEach({ (layoutAttributes) in + setFrame(forLayoutAttributes: layoutAttributes) + }) + return layoutAttributesObjects + } + + + // MARK: - 👷 Private layout helpers + + /// Sets the frame for the passed layout attributes object by calling the `layoutAttributesForItem(at:)` function. + private func setFrame(forLayoutAttributes layoutAttributes: UICollectionViewLayoutAttributes) { + if layoutAttributes.representedElementCategory == .cell { // Do not modify header views etc. + let indexPath = layoutAttributes.indexPath + if let newFrame = layoutAttributesForItem(at: indexPath)?.frame { + layoutAttributes.frame = newFrame + } + } + } + + /// A function to access the `super` implementation of `layoutAttributesForItem(at:)` externally. + /// + /// - Parameter indexPath: The index path of the item for which to return the layout attributes. + /// - Returns: The unmodified layout attributes for the item at the specified index path + /// as computed by `UICollectionViewFlowLayout`. + fileprivate func originalLayoutAttribute(forItemAt indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + return super.layoutAttributesForItem(at: indexPath) + } + + /// Determines if the `firstItemAttributes`' frame is in the same line + /// as the `secondItemAttributes`' frame. + /// + /// - Parameters: + /// - firstItemAttributes: The first layout attributes object to be compared. + /// - secondItemAttributes: The second layout attributes object to be compared. + /// - Returns: `true` if the frames of the two layout attributes are in the same line, else `false`. + /// `false` is also returned when the layout's `collectionView` property is `nil`. + fileprivate func isFrame(for firstItemAttributes: UICollectionViewLayoutAttributes, inSameLineAsFrameFor secondItemAttributes: UICollectionViewLayoutAttributes) -> Bool { + guard let lineWidth = contentWidth else { + return false + } + let firstItemFrame = firstItemAttributes.frame + let lineFrame = CGRect(x: sectionInset.left, + y: firstItemFrame.origin.y, + width: lineWidth, + height: firstItemFrame.size.height) + return lineFrame.intersects(secondItemAttributes.frame) + } + + /// Determines the layout attributes objects for all items displayed in the same line as the item + /// represented by the passed `layoutAttributes` object. + /// + /// - Parameter layoutAttributes: The layout attributed that represents the reference item. + /// - Returns: The layout attributes objects representing all other items in the same line. + /// The passed `layoutAttributes` object itself is always contained in the returned array. + fileprivate func layoutAttributes(forItemsInLineWith layoutAttributes: UICollectionViewLayoutAttributes) -> [UICollectionViewLayoutAttributes] { + guard let lineWidth = contentWidth else { + return [layoutAttributes] + } + var lineFrame = layoutAttributes.frame + lineFrame.origin.x = sectionInset.left + lineFrame.size.width = lineWidth + return super.layoutAttributesForElements(in: lineFrame) ?? [] + } + + /// Copmutes the alignment axis with which to align the items represented by the `layoutAttributes` objects vertically. + /// + /// - Parameter layoutAttributes: The layout attributes objects to be vertically aligned. + /// - Returns: The axis with respect to which the layout attributes can be aligned + /// or `nil` if the `layoutAttributes` array is empty. + private func verticalAlignmentAxisForLine(with layoutAttributes: [UICollectionViewLayoutAttributes]) -> AlignmentAxis? { + + guard let firstAttribute = layoutAttributes.first else { + return nil + } + + switch verticalAlignment { + case .top: + let minY = layoutAttributes.reduce(CGFloat.greatestFiniteMagnitude) { min($0, $1.frame.minY) } + return AlignmentAxis(alignment: .top, position: minY) + + case .bottom: + let maxY = layoutAttributes.reduce(0) { max($0, $1.frame.maxY) } + return AlignmentAxis(alignment: .bottom, position: maxY) + + default: + let centerY = firstAttribute.center.y + return AlignmentAxis(alignment: .center, position: centerY) + } + } + + /// Computes the axis with which to align the item represented by the `currentLayoutAttributes` vertically. + /// + /// - Parameter currentLayoutAttributes: The layout attributes representing the item to be vertically aligned. + /// - Returns: The axis with respect to which the item can be aligned. + fileprivate func verticalAlignmentAxis(for currentLayoutAttributes: UICollectionViewLayoutAttributes) -> AlignmentAxis? { + let layoutAttributesInLine = layoutAttributes(forItemsInLineWith: currentLayoutAttributes) + // It's okay to force-unwrap here because we pass a non-empty array. + return verticalAlignmentAxisForLine(with: layoutAttributesInLine) + } + + /// Creates a deep copy of the passed array by copying all its items. + /// + /// - Parameter layoutAttributesArray: The array to be copied. + /// - Returns: A deep copy of the passed array. + private func copy(_ layoutAttributesArray: [UICollectionViewLayoutAttributes]?) -> [UICollectionViewLayoutAttributes]? { + return layoutAttributesArray?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes] + } + +} + + +// MARK: - 👷 Layout attributes helpers + +fileprivate extension UICollectionViewLayoutAttributes { + + private var currentSection: Int { + return indexPath.section + } + + private var currentItem: Int { + return indexPath.item + } + + /// The index path for the item preceding the item represented by this layout attributes object. + private var precedingIndexPath: IndexPath { + return IndexPath(item: currentItem - 1, section: currentSection) + } + + /// The index path for the item following the item represented by this layout attributes object. + private var followingIndexPath: IndexPath { + return IndexPath(item: currentItem + 1, section: currentSection) + } + + /// Checks if the item represetend by this layout attributes object is the first item in the line. + /// + /// - Parameter collectionViewLayout: The layout for which to perform the check. + /// - Returns: `true` if the represented item is the first item in the line, else `false`. + func isRepresentingFirstItemInLine(collectionViewLayout: AlignedCollectionViewFlowLayout) -> Bool { + if currentItem <= 0 { + return true + } + else { + if let layoutAttributesForPrecedingItem = collectionViewLayout.originalLayoutAttribute(forItemAt: precedingIndexPath) { + return !collectionViewLayout.isFrame(for: self, inSameLineAsFrameFor: layoutAttributesForPrecedingItem) + } + else { + return true + } + } + } + + /// Checks if the item represetend by this layout attributes object is the last item in the line. + /// + /// - Parameter collectionViewLayout: The layout for which to perform the check. + /// - Returns: `true` if the represented item is the last item in the line, else `false`. + func isRepresentingLastItemInLine(collectionViewLayout: AlignedCollectionViewFlowLayout) -> Bool { + guard let itemCount = collectionViewLayout.collectionView?.numberOfItems(inSection: currentSection) else { + return false + } + + if currentItem >= itemCount - 1 { + return true + } + else { + if let layoutAttributesForFollowingItem = collectionViewLayout.originalLayoutAttribute(forItemAt: followingIndexPath) { + return !collectionViewLayout.isFrame(for: self, inSameLineAsFrameFor: layoutAttributesForFollowingItem) + } + else { + return true + } + } + } + + /// Moves the layout attributes object's frame so that it is aligned horizontally with the alignment axis. + func align(toAlignmentAxis alignmentAxis: AlignmentAxis) { + switch alignmentAxis.alignment { + case .left: + frame.origin.x = alignmentAxis.position + case .right: + frame.origin.x = alignmentAxis.position - frame.size.width + default: + break + } + } + + /// Moves the layout attributes object's frame so that it is aligned vertically with the alignment axis. + func align(toAlignmentAxis alignmentAxis: AlignmentAxis) { + switch alignmentAxis.alignment { + case .top: + frame.origin.y = alignmentAxis.position + case .bottom: + frame.origin.y = alignmentAxis.position - frame.size.height + default: + center.y = alignmentAxis.position + } + } + + /// Positions the frame right of the preceding item's frame, leaving a spacing between the frames + /// as defined by the collection view layout's `minimumInteritemSpacing`. + /// + /// - Parameter collectionViewLayout: The layout on which to perfom the calculations. + private func alignToPrecedingItem(collectionViewLayout: AlignedCollectionViewFlowLayout) { + let itemSpacing = collectionViewLayout.minimumInteritemSpacing + + if let precedingItemAttributes = collectionViewLayout.layoutAttributesForItem(at: precedingIndexPath) { + frame.origin.x = precedingItemAttributes.frame.maxX + itemSpacing + } + } + + /// Positions the frame left of the following item's frame, leaving a spacing between the frames + /// as defined by the collection view layout's `minimumInteritemSpacing`. + /// + /// - Parameter collectionViewLayout: The layout on which to perfom the calculations. + private func alignToFollowingItem(collectionViewLayout: AlignedCollectionViewFlowLayout) { + let itemSpacing = collectionViewLayout.minimumInteritemSpacing + + if let followingItemAttributes = collectionViewLayout.layoutAttributesForItem(at: followingIndexPath) { + frame.origin.x = followingItemAttributes.frame.minX - itemSpacing - frame.size.width + } + } + + /// Aligns the frame horizontally as specified by the collection view layout's `horizontalAlignment`. + /// + /// - Parameters: + /// - collectionViewLayout: The layout providing the alignment information. + func alignHorizontally(collectionViewLayout: AlignedCollectionViewFlowLayout) { + + guard let alignmentAxis = collectionViewLayout.alignmentAxis else { + return + } + + switch collectionViewLayout.horizontalAlignment { + + case .left: + if isRepresentingFirstItemInLine(collectionViewLayout: collectionViewLayout) { + align(toAlignmentAxis: alignmentAxis) + } else { + alignToPrecedingItem(collectionViewLayout: collectionViewLayout) + } + + case .right: + if isRepresentingLastItemInLine(collectionViewLayout: collectionViewLayout) { + align(toAlignmentAxis: alignmentAxis) + } else { + alignToFollowingItem(collectionViewLayout: collectionViewLayout) + } + + default: + return + } + } + + /// Aligns the frame vertically as specified by the collection view layout's `verticalAlignment`. + /// + /// - Parameter collectionViewLayout: The layout providing the alignment information. + func alignVertically(collectionViewLayout: AlignedCollectionViewFlowLayout) { + guard let alignmentAxis = collectionViewLayout.verticalAlignmentAxis(for: self) else { + return + } + align(toAlignmentAxis: alignmentAxis) + } + +} diff --git a/BeeReel/Thirdparty/JXUUID/JXUUID.h b/BeeReel/Thirdparty/JXUUID/JXUUID.h index 242e0b6..4ea9e47 100644 --- a/BeeReel/Thirdparty/JXUUID/JXUUID.h +++ b/BeeReel/Thirdparty/JXUUID/JXUUID.h @@ -15,6 +15,6 @@ /** 重新安装app后,会发生变化 */ -+ (nonnull NSString *)systemUUID; ++ (nonnull NSString *)idfv; @end diff --git a/BeeReel/Thirdparty/JXUUID/JXUUID.m b/BeeReel/Thirdparty/JXUUID/JXUUID.m index be414ea..703c2f3 100644 --- a/BeeReel/Thirdparty/JXUUID/JXUUID.m +++ b/BeeReel/Thirdparty/JXUUID/JXUUID.m @@ -23,7 +23,7 @@ static NSString *const uuidKey = @"com.JXUUID"; uuid = [PDKeyChain objectForKey:uuidKey]; if (uuid && uuid.length > 0) { } else { - uuid = [[NSUUID UUID] UUIDString]; + uuid = [self idfv]; [PDKeyChain setObject:uuid forKey:uuidKey]; } }); @@ -31,15 +31,10 @@ static NSString *const uuidKey = @"com.JXUUID"; } + (nonnull NSString *)idfa { - static NSString *idfa; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - idfa = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; - }); - return idfa; + return [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString]; } -+ (nonnull NSString *)systemUUID ++ (nonnull NSString *)idfv { return [UIDevice currentDevice].identifierForVendor.UUIDString; } diff --git a/Podfile b/Podfile index 6897410..5308ba5 100644 --- a/Podfile +++ b/Podfile @@ -21,7 +21,6 @@ target 'BeeReel' do pod 'Kingfisher' #图片加载 pod 'SnapKit' #布局 pod 'Toast' #吐司提示 -# pod 'YYKit' #工具类 pod 'SJVideoPlayer' #播放器 pod 'SJMediaCacheServer' #播放器缓存 pod 'WMZPageController' #分页控制器 @@ -30,5 +29,6 @@ target 'BeeReel' do pod 'FSPagerView' #banner pod 'MJRefresh' #刷新控件 pod 'HWPanModal' #底部弹出控制器 + pod 'FDFullscreenPopGesture' #全屏手势返回 end