第一次上传,首页开发
4
.gitignore
vendored
@ -17,6 +17,9 @@ xcuserdata/
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
*.xcworkspace
|
||||
Podfile.lock
|
||||
Pods/
|
||||
|
||||
# Swift Package Manager
|
||||
#
|
||||
@ -38,7 +41,6 @@ playground.xcworkspace
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
#
|
||||
# Pods/
|
||||
#
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
# *.xcworkspace
|
||||
|
1051
BeeReel.xcodeproj/project.pbxproj
Normal file
44
BeeReel/Base/Controller/BRNavigationController.swift
Normal file
@ -0,0 +1,44 @@
|
||||
//
|
||||
// BRNavigationController.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRNavigationController: UINavigationController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
|
||||
if children.count > 0 {
|
||||
viewController.hidesBottomBarWhenPushed = true
|
||||
}
|
||||
super.pushViewController(viewController, animated: animated)
|
||||
}
|
||||
|
||||
override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) {
|
||||
for (index, value) in viewControllers.enumerated() {
|
||||
if index != 0 {
|
||||
value.hidesBottomBarWhenPushed = true
|
||||
}
|
||||
}
|
||||
super.setViewControllers(viewControllers, animated: animated)
|
||||
}
|
||||
|
||||
|
||||
//MARK:-------------- 状态栏样式 --------------
|
||||
override var childForStatusBarStyle: UIViewController? {
|
||||
return self.topViewController
|
||||
}
|
||||
|
||||
override var childForStatusBarHidden: UIViewController? {
|
||||
return self.topViewController
|
||||
}
|
||||
|
||||
}
|
118
BeeReel/Base/Controller/BRTabBarController.swift
Normal file
@ -0,0 +1,118 @@
|
||||
//
|
||||
// BRTabBarController.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRTabBarController: UITabBarController {
|
||||
|
||||
private var ignoreNextSelection = false
|
||||
|
||||
override var selectedViewController: UIViewController? {
|
||||
willSet {
|
||||
guard let newValue = newValue else {
|
||||
// if newValue == nil ...
|
||||
return
|
||||
}
|
||||
|
||||
guard !ignoreNextSelection else {
|
||||
ignoreNextSelection = false
|
||||
return
|
||||
}
|
||||
|
||||
guard let tabBar = self.tabBar as? BRTabBar, let index = viewControllers?.firstIndex(of: newValue) else {
|
||||
return
|
||||
}
|
||||
tabBar.select(itemAtIndex: index, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
override var selectedIndex: Int {
|
||||
willSet {
|
||||
guard let tabBar = self.tabBar as? BRTabBar else {
|
||||
return
|
||||
}
|
||||
guard !ignoreNextSelection else {
|
||||
ignoreNextSelection = false
|
||||
return
|
||||
}
|
||||
|
||||
tabBar.select(itemAtIndex: newValue, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
let tabBar = BRTabBar()
|
||||
tabBar.delegate = self
|
||||
self.setValue(tabBar, forKey: "tabBar")
|
||||
self.tabBar.isTranslucent = true
|
||||
|
||||
|
||||
br_setup()
|
||||
}
|
||||
|
||||
|
||||
//MARK:-------------- 状态栏样式 --------------
|
||||
override var childForStatusBarStyle: UIViewController? {
|
||||
return self.selectedViewController
|
||||
}
|
||||
|
||||
override var childForStatusBarHidden: UIViewController? {
|
||||
return self.selectedViewController
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRTabBarController {
|
||||
|
||||
private func br_setup() {
|
||||
let nav1 = createNavigationController(viewController: BRHomeViewController(), title: "首页".localized, image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected"))
|
||||
let nav2 = createNavigationController(viewController: BRViewController(), title: "推荐".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected"))
|
||||
let nav3 = createNavigationController(viewController: BRViewController(), title: "首页".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_03_selected"))
|
||||
let nav4 = createNavigationController(viewController: BRViewController(), title: "首页".localized, image: UIImage(named: "tabbar_icon_04"), selectedImage: UIImage(named: "tabbar_icon_04_selected"))
|
||||
|
||||
viewControllers = [nav1, nav2, nav3, nav4]
|
||||
|
||||
}
|
||||
|
||||
private func createNavigationController(viewController: UIViewController, title: String?, image: UIImage?, selectedImage: UIImage?) -> UINavigationController {
|
||||
let tabBarItem = BRTabBarItem()
|
||||
tabBarItem.title = title
|
||||
tabBarItem.image = image
|
||||
tabBarItem.selectedImage = selectedImage
|
||||
|
||||
let nav = BRNavigationController(rootViewController: viewController)
|
||||
nav.tabBarItem = tabBarItem
|
||||
return nav
|
||||
}
|
||||
}
|
||||
|
||||
extension BRTabBarController {
|
||||
|
||||
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
|
||||
guard let idx = tabBar.items?.firstIndex(of: item) else {
|
||||
return;
|
||||
}
|
||||
if let vc = viewControllers?[idx] {
|
||||
ignoreNextSelection = true
|
||||
selectedIndex = idx
|
||||
delegate?.tabBarController?(self, didSelect: vc)
|
||||
}
|
||||
}
|
||||
|
||||
override func tabBar(_ tabBar: UITabBar, willBeginCustomizing items: [UITabBarItem]) {
|
||||
if let tabBar = tabBar as? BRTabBar {
|
||||
tabBar.updateLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override func tabBar(_ tabBar: UITabBar, didEndCustomizing items: [UITabBarItem], changed: Bool) {
|
||||
if let tabBar = tabBar as? BRTabBar {
|
||||
tabBar.updateLayout()
|
||||
}
|
||||
}
|
||||
}
|
54
BeeReel/Base/Controller/BRViewController.swift
Normal file
@ -0,0 +1,54 @@
|
||||
//
|
||||
// BRViewController.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRViewController: UIViewController {
|
||||
|
||||
private(set) var isViewDidLoad = false
|
||||
private(set) var isDidAppear = false
|
||||
private(set) var hasViewDidAppear = false
|
||||
|
||||
var statusBarStyle: UIStatusBarStyle = .darkContent {
|
||||
didSet {
|
||||
self.setNeedsStatusBarAppearanceUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.edgesForExtendedLayout = []
|
||||
self.isViewDidLoad = true
|
||||
self.view.backgroundColor = .backgroundColor()
|
||||
|
||||
}
|
||||
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
isDidAppear = true
|
||||
hasViewDidAppear = true
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
isDidAppear = false
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
isDidAppear = false
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return statusBarStyle
|
||||
}
|
||||
|
||||
func handleHeaderRefresh(_ completer: (() -> Void)?) {}
|
||||
|
||||
func handleFooterRefresh(_ completer: (() -> Void)?) {}
|
||||
}
|
47
BeeReel/Base/Define/BRDefine.swift
Normal file
@ -0,0 +1,47 @@
|
||||
//
|
||||
// BRDefine.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
//MARK:-------------- 系统版本号 --------------
|
||||
///当前系统版本号
|
||||
let kBROsVersion: String = UIDevice.current.systemVersion
|
||||
let kBRAPPBundleIdentifier: String = (Bundle.main.infoDictionary!["CFBundleIdentifier"] as? String) ?? "0"
|
||||
|
||||
///app版本号
|
||||
public let kBRAPPVersion: String = (Bundle.main.infoDictionary!["CFBundleShortVersionString"] as? String) ?? "0"
|
||||
public let kSBAPPBundleVersion: String = (Bundle.main.infoDictionary!["CFBundleVersion"] as? String) ?? "0"
|
||||
|
||||
public let kBRAPPBundleName: String = (Bundle.main.infoDictionary!["CFBundleName"] as? String) ?? ""
|
||||
public let kBRAPPName: String = (Bundle.main.infoDictionary!["CFBundleDisplayName"] as? String) ?? ""
|
||||
|
||||
|
||||
//MARK: ------- 打印信息 ----------
|
||||
#if DEBUG
|
||||
public func brLog(message: Any? , file: String = #file, function: String = #function, line: Int = #line) {
|
||||
print("\n\(Date(timeIntervalSinceNow: 8 * 60 * 60)) \(file.components(separatedBy: "/").last ?? "") \(function) \(line): \(message ?? "")")
|
||||
}
|
||||
#else
|
||||
public func brLog(message: Any?) { }
|
||||
#endif
|
||||
|
||||
public func br_swizzled_instanceMethod(_ prefix: String, oldClass: Swift.AnyClass!, oldSelector: String, newClass: Swift.AnyClass) {
|
||||
let newSelector = prefix + "_" + oldSelector;
|
||||
let originalSelector = NSSelectorFromString(oldSelector)
|
||||
let swizzledSelector = NSSelectorFromString(newSelector)
|
||||
|
||||
let originalMethod = class_getInstanceMethod(oldClass, originalSelector)
|
||||
let swizzledMethod = class_getInstanceMethod(newClass, swizzledSelector)
|
||||
|
||||
let isAdd = class_addMethod(oldClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
|
||||
|
||||
if isAdd {
|
||||
class_replaceMethod(newClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
|
||||
}else {
|
||||
method_exchangeImplementations(originalMethod!, swizzledMethod!)
|
||||
}
|
||||
}
|
17
BeeReel/Base/Define/BRUserDefaultsKey.swift
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// BRUserDefaultsKey.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
|
||||
///登录token
|
||||
let kBRLoginTokenDefaultsKey = "kBRLoginTokenDefaultsKey"
|
||||
|
||||
///用户信息
|
||||
let kBRLoginUserInfoDefaultsKey = "kBRLoginUserInfoDefaultsKey"
|
||||
|
||||
///分辨率
|
||||
let kBRVideoRevolutionDefaultsKey = "kBRVideoRevolutionDefaultsKey"
|
||||
|
19
BeeReel/Base/Extension/AttributedString+BRAdd.swift
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// AttributedString+BRAdd.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension AttributedString {
|
||||
|
||||
static func br_createAttributedString(string: String, color: UIColor, font: UIFont) -> AttributedString {
|
||||
return AttributedString(string, attributes: AttributeContainer([
|
||||
.foregroundColor: color,
|
||||
.font : font
|
||||
]))
|
||||
}
|
||||
|
||||
}
|
65
BeeReel/Base/Extension/CGMutablePath+BRRoundedCorner.swift
Normal file
@ -0,0 +1,65 @@
|
||||
//
|
||||
// CGMutablePath+BRRoundedCorner.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
struct BRRoundedCorner {
|
||||
var topLeft:CGFloat = 0
|
||||
var topRight:CGFloat = 0
|
||||
var bottomLeft:CGFloat = 0
|
||||
var bottomRight:CGFloat = 0
|
||||
|
||||
public static let zero = BRRoundedCorner(topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0)
|
||||
public init(topLeft: CGFloat, topRight:CGFloat, bottomLeft:CGFloat, bottomRight:CGFloat) {
|
||||
self.topLeft = topLeft
|
||||
self.topRight = topRight
|
||||
self.bottomLeft = bottomLeft
|
||||
self.bottomRight = bottomRight
|
||||
}
|
||||
static func ==(v1:BRRoundedCorner, v2:BRRoundedCorner) -> Bool {
|
||||
return v1.bottomLeft == v2.bottomLeft
|
||||
&& v1.bottomRight == v2.bottomRight
|
||||
&& v1.topLeft == v2.topLeft
|
||||
&& v1.topRight == v2.topRight
|
||||
}
|
||||
static func !=(v1:BRRoundedCorner, v2:BRRoundedCorner) -> Bool {
|
||||
return !(v1 == v2)
|
||||
}
|
||||
}
|
||||
|
||||
extension CGMutablePath {
|
||||
func addRadiusRectangle(_ circulars: BRRoundedCorner, rect: CGRect) {
|
||||
let minX = rect.minX
|
||||
let minY = rect.minY
|
||||
let maxX = rect.maxX
|
||||
let maxY = rect.maxY
|
||||
|
||||
//获取四个圆心
|
||||
let topLeftCenterX = minX + circulars.topLeft
|
||||
let topLeftCenterY = minY + circulars.topLeft
|
||||
|
||||
let topRightCenterX = maxX - circulars.topRight
|
||||
let topRightCenterY = minY + circulars.topRight
|
||||
|
||||
let bottomLeftCenterX = minX + circulars.bottomLeft
|
||||
let bottomLeftCenterY = maxY - circulars.bottomLeft
|
||||
|
||||
let bottomRightCenterX = maxX - circulars.bottomRight
|
||||
let bottomRightCenterY = maxY - circulars.bottomRight
|
||||
|
||||
//顶 左
|
||||
addArc(center: CGPoint(x: topLeftCenterX, y: topLeftCenterY), radius: circulars.topLeft, startAngle: CGFloat.pi, endAngle: CGFloat.pi * 3 / 2, clockwise: false)
|
||||
//顶右
|
||||
addArc(center: CGPoint(x: topRightCenterX, y: topRightCenterY), radius: circulars.topRight, startAngle: CGFloat.pi * 3 / 2, endAngle: 0, clockwise: false)
|
||||
//底右
|
||||
addArc(center: CGPoint(x: bottomRightCenterX, y: bottomRightCenterY), radius: circulars.bottomRight, startAngle: 0, endAngle: CGFloat.pi / 2, clockwise: false)
|
||||
//底左
|
||||
addArc(center: CGPoint(x: bottomLeftCenterX, y: bottomLeftCenterY), radius: circulars.bottomLeft, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi, clockwise: false)
|
||||
closeSubpath();
|
||||
|
||||
}
|
||||
}
|
12
BeeReel/Base/Extension/String+BRAdd.swift
Normal file
@ -0,0 +1,12 @@
|
||||
//
|
||||
// String+BRAdd.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import SmartCodable
|
||||
|
||||
extension String: SmartCodable {
|
||||
|
||||
}
|
53
BeeReel/Base/Extension/UIColor+BRAdd.swift
Normal file
@ -0,0 +1,53 @@
|
||||
//
|
||||
// UIColor.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIColor {
|
||||
|
||||
static func backgroundColor() -> UIColor {
|
||||
return colorEFEFF5()
|
||||
}
|
||||
|
||||
static func themeColor() -> UIColor {
|
||||
return color1C1C1C()
|
||||
}
|
||||
}
|
||||
|
||||
extension UIColor {
|
||||
static func colorFFFFFF(alpha: CGFloat = 1) -> UIColor {
|
||||
return UIColor(rgb: 0xFFFFFF, alpha: alpha)
|
||||
}
|
||||
|
||||
static func color000000(alpha: CGFloat = 1) -> UIColor {
|
||||
return UIColor(rgb: 0x000000, alpha: alpha)
|
||||
}
|
||||
|
||||
static func color1C1C1C(alpha: CGFloat = 1) -> UIColor {
|
||||
return UIColor(rgb: 0x1C1C1C, alpha: alpha)
|
||||
}
|
||||
|
||||
static func colorEFEFF5(alpha: CGFloat = 1) -> UIColor {
|
||||
return UIColor(rgb: 0xEFEFF5, alpha: alpha)
|
||||
}
|
||||
|
||||
static func colorD3D3D3(alpha: CGFloat = 1) -> UIColor {
|
||||
return UIColor(rgb: 0xD3D3D3, alpha: alpha)
|
||||
}
|
||||
|
||||
static func colorF1FF94(alpha: CGFloat = 1) -> UIColor {
|
||||
return UIColor(rgb: 0xF1FF94, alpha: alpha)
|
||||
}
|
||||
|
||||
static func color899D00(alpha: CGFloat = 1) -> UIColor {
|
||||
return UIColor(rgb: 0x899D00, alpha: alpha)
|
||||
}
|
||||
|
||||
static func colorFF7489(alpha: CGFloat = 1) -> UIColor {
|
||||
return UIColor(rgb: 0xFF7489, alpha: alpha)
|
||||
}
|
||||
}
|
71
BeeReel/Base/Extension/UIDevice+BRAdd.swift
Normal file
@ -0,0 +1,71 @@
|
||||
//
|
||||
// UIDevice+BRAdd.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIDevice {
|
||||
//http://theiphonewiki.com/wiki/Models
|
||||
static func br_machineModelName() -> String {
|
||||
guard let machineModel = UIDevice.current.machineModel else { return "" }
|
||||
let map = [
|
||||
"iPhone1,1" : "iPhone",
|
||||
"iPhone1,2" : "iPhone 3G",
|
||||
"iPhone2,1" : "iPhone 3GS",
|
||||
"iPhone3,1" : "iPhone 4",
|
||||
"iPhone3,2" : "iPhone 4",
|
||||
"iPhone3,3" : "iPhone 4",
|
||||
"iPhone4,1" : "iPhone4,1",
|
||||
"iPhone5,1" : "iPhone 5",
|
||||
"iPhone5,2" : "iPhone 5",
|
||||
"iPhone5,3" : "iPhone 5c",
|
||||
"iPhone5,4" : "iPhone 5c",
|
||||
"iPhone6,1" : "iPhone 5s",
|
||||
"iPhone6,2" : "iPhone 5s",
|
||||
"iPhone7,2" : "iPhone 6",
|
||||
"iPhone7,1" : "iPhone 6 Plus",
|
||||
"iPhone8,1" : "iPhone 6s",
|
||||
"iPhone8,2" : "iPhone 6s Plus",
|
||||
"iPhone8,4" : "iPhone SE (1st generation)",
|
||||
"iPhone9,1" : "iPhone 7",
|
||||
"iPhone9,3" : "iPhone 7",
|
||||
"iPhone9,2" : "iPhone 7 Plus",
|
||||
"iPhone9,4" : "iPhone 7 Plus",
|
||||
"iPhone10,1" : "iPhone 8",
|
||||
"iPhone10,4" : "iPhone 8",
|
||||
"iPhone10,2" : "iPhone 8 Plus",
|
||||
"iPhone10,5" : "iPhone 8 Plus",
|
||||
"iPhone10,3" : "iPhone X",
|
||||
"iPhone10,6" : "iPhone X",
|
||||
"iPhone11,8" : "iPhone XR",
|
||||
"iPhone11,2" : "iPhone11,2",
|
||||
"iPhone11,6" : "iPhone XS Max",
|
||||
"iPhone11,4" : "iPhone XS Max",
|
||||
"iPhone12,1" : "iPhone 11",
|
||||
"iPhone12,3" : "iPhone 11 Pro",
|
||||
"iPhone12,5" : "iPhone 11 Pro Max",
|
||||
"iPhone12,8" : "iPhone SE (2nd generation)",
|
||||
"iPhone13,1" : "iPhone 12 mini",
|
||||
"iPhone13,2" : "iPhone13,2",
|
||||
"iPhone13,3" : "iPhone 12 Pro",
|
||||
"iPhone13,4" : "iPhone 12 Pro Max",
|
||||
"iPhone14,4" : "iPhone 13 mini",
|
||||
"iPhone14,5" : "iPhone 13",
|
||||
"iPhone14,2" : "iPhone 13 Pro",
|
||||
"iPhone14,3" : "iPhone 13 Pro Max",
|
||||
"iPhone14,6" : "iPhone SE (3rd generation)",
|
||||
"iPhone14,7" : "iPhone 14",
|
||||
"iPhone14,8" : "iPhone 14 Plus",
|
||||
"iPhone15,2" : "iPhone 14 Pro",
|
||||
"iPhone15,3" : "iPhone 14 Pro Max",
|
||||
]
|
||||
if let name = map[machineModel] {
|
||||
return name
|
||||
}
|
||||
|
||||
return machineModel
|
||||
}
|
||||
}
|
24
BeeReel/Base/Extension/UIFont+BRAdd.swift
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// UIFont+BRAdd.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
extension UIFont {
|
||||
|
||||
static func fontRegular(ofSize: CGFloat) -> UIFont {
|
||||
return .systemFont(ofSize: ofSize, weight: .regular)
|
||||
}
|
||||
|
||||
static func fontMedium(ofSize: CGFloat) -> UIFont {
|
||||
return .systemFont(ofSize: ofSize, weight: .medium)
|
||||
}
|
||||
|
||||
static func fontBold(ofSize: CGFloat) -> UIFont {
|
||||
return .systemFont(ofSize: ofSize, weight: .bold)
|
||||
}
|
||||
}
|
24
BeeReel/Base/Extension/UIImageView+BRAdd.swift
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// UIImageView+BRAdd.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Kingfisher
|
||||
|
||||
extension UIImageView {
|
||||
func br_setImage(url: String?, placeholder: UIImage? = nil, completer: ((_ image: UIImage?, _ url: URL?) -> Void)? = nil) {
|
||||
|
||||
self.kf.setImage(with: URL(string: url ?? ""), placeholder: placeholder, options: nil) { result in
|
||||
switch result {
|
||||
case .success(let value):
|
||||
completer?(value.image, value.source.url)
|
||||
default :
|
||||
completer?(nil, nil)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
49
BeeReel/Base/Extension/UIScreen+BRAdd.swift
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// UIScreen+BRAdd.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIScreen {
|
||||
|
||||
static var screen: UIScreen {
|
||||
if let screen = BRAppTool.windowScene?.screen {
|
||||
return screen
|
||||
} else {
|
||||
return UIScreen.main
|
||||
}
|
||||
}
|
||||
|
||||
static var width: CGFloat {
|
||||
return screen.bounds.width
|
||||
}
|
||||
|
||||
static var height: CGFloat {
|
||||
return screen.bounds.height
|
||||
}
|
||||
|
||||
static var statusBarHeight: CGFloat {
|
||||
let top = BRAppTool.windowScene?.windows.first?.safeAreaInsets.top ?? 20
|
||||
return top
|
||||
}
|
||||
|
||||
static var tabbarSafeBottomMargin: CGFloat {
|
||||
let bottom = BRAppTool.windowScene?.windows.first?.safeAreaInsets.bottom ?? 0
|
||||
return bottom
|
||||
}
|
||||
|
||||
static var navBarHeight: CGFloat {
|
||||
return statusBarHeight + 44
|
||||
}
|
||||
|
||||
static var tabBarHeight: CGFloat {
|
||||
return tabbarSafeBottomMargin + 49
|
||||
}
|
||||
|
||||
static var customTabBarHeight: CGFloat {
|
||||
return tabbarSafeBottomMargin + 50
|
||||
}
|
||||
}
|
18
BeeReel/Base/Extension/UIStackView+BRAdd.swift
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// UIStackView+BRAdd.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/27.
|
||||
//
|
||||
|
||||
extension UIStackView {
|
||||
|
||||
func br_removeAllArrangedSubview() {
|
||||
let arrangedSubviews = self.arrangedSubviews
|
||||
|
||||
arrangedSubviews.forEach {
|
||||
self.removeArrangedSubview($0)
|
||||
$0.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
94
BeeReel/Base/Extension/UIView+BRAdd.swift
Normal file
@ -0,0 +1,94 @@
|
||||
//
|
||||
// UIView+BRAdd.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SnapKit
|
||||
|
||||
extension UIView {
|
||||
fileprivate struct AssociatedKeys {
|
||||
static var br_roundedCorner: Int?
|
||||
static var br_effect: Int?
|
||||
}
|
||||
|
||||
@objc public static func vp_Awake() {
|
||||
br_swizzled_instanceMethod("br", oldClass: self, oldSelector: "layoutSubviews", newClass: self)
|
||||
}
|
||||
|
||||
@objc func br_layoutSubviews() {
|
||||
br_layoutSubviews()
|
||||
|
||||
_updateRoundedCorner()
|
||||
|
||||
if let effectView = effectView, effectView.frame != self.bounds {
|
||||
effectView.frame = self.bounds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: -------------- 圆角 --------------
|
||||
extension UIView {
|
||||
|
||||
|
||||
private var roundedCorner: BRRoundedCorner? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &AssociatedKeys.br_roundedCorner) as? BRRoundedCorner
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &AssociatedKeys.br_roundedCorner, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
///设置圆角
|
||||
func setRoundedCorner(topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) {
|
||||
//清空其它设置方法
|
||||
self.roundedCorner = BRRoundedCorner(topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight)
|
||||
_updateRoundedCorner()
|
||||
}
|
||||
|
||||
private func _updateRoundedCorner() {
|
||||
guard let roundedCorner = self.roundedCorner else { return }
|
||||
let rect = self.bounds
|
||||
|
||||
let path = CGMutablePath()
|
||||
path.addRadiusRectangle(roundedCorner, rect: rect)
|
||||
|
||||
let maskLayer = CAShapeLayer()
|
||||
maskLayer.frame = self.bounds
|
||||
maskLayer.path = path
|
||||
self.layer.mask = maskLayer
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- 模糊效果 --------------
|
||||
extension UIView {
|
||||
private var effectView: UIVisualEffectView? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &AssociatedKeys.br_effect) as? UIVisualEffectView
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, &AssociatedKeys.br_effect, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
|
||||
///添加模糊效果
|
||||
func br_addEffectView(style: UIBlurEffect.Style = .light) {
|
||||
if self.effectView == nil {
|
||||
let blur = UIBlurEffect(style: style)
|
||||
let effectView = UIVisualEffectView(effect: blur)
|
||||
effectView.isUserInteractionEnabled = false
|
||||
self.addSubview(effectView)
|
||||
self.sendSubviewToBack(effectView)
|
||||
|
||||
self.effectView = effectView
|
||||
}
|
||||
}
|
||||
///删除模糊效果
|
||||
func br_removeEffectView() {
|
||||
self.effectView?.removeFromSuperview()
|
||||
self.effectView = nil
|
||||
}
|
||||
}
|
44
BeeReel/Base/Extension/UserDefaults+BRAdd.swift
Normal file
@ -0,0 +1,44 @@
|
||||
//
|
||||
// UserDefaults+BRAdd.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
extension UserDefaults {
|
||||
|
||||
static func br_setObject(_ obj: NSSecureCoding?, forKey key: String) {
|
||||
let defaults = UserDefaults.standard
|
||||
|
||||
guard let obj = obj else {
|
||||
defaults.removeObject(forKey: key)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let data = try NSKeyedArchiver.archivedData(withRootObject: obj, requiringSecureCoding: true)
|
||||
defaults.set(data, forKey: key)
|
||||
} catch {
|
||||
print("Error archiving object: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
static func br_object<T: NSObject & NSSecureCoding>(forKey key: String, as type: T.Type) -> T? {
|
||||
let defaults = UserDefaults.standard
|
||||
|
||||
guard let data = defaults.data(forKey: key) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
do {
|
||||
let object = try NSKeyedUnarchiver.unarchivedObject(ofClass: type, from: data)
|
||||
return object
|
||||
} catch {
|
||||
print("Error unarchiving object: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
22
BeeReel/Base/Model/BRListModel.swift
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// BRListModel.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/26.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SmartCodable
|
||||
|
||||
class BRListModel<T: SmartCodable>: BRModel, SmartCodable {
|
||||
var list: [T]?
|
||||
var pagination: BRListPaginationModel?
|
||||
}
|
||||
|
||||
|
||||
class BRListPaginationModel: BRModel, SmartCodable {
|
||||
var current_page: Int?
|
||||
var page_size: Int?
|
||||
var page_total: Int?
|
||||
var total_size: Int?
|
||||
}
|
15
BeeReel/Base/Model/BRModel.swift
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// BRModel.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRModel: NSObject {
|
||||
|
||||
required override init() {
|
||||
super.init()
|
||||
}
|
||||
}
|
32
BeeReel/Base/Network/API/BRHomeAPI.swift
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// BRHomeAPI.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/26.
|
||||
//
|
||||
|
||||
|
||||
class BRHomeAPI {
|
||||
///首页数据
|
||||
static func requestHomeData(completer: ((_ list: [BRHomeModuleItem]?) -> Void)?) {
|
||||
var param = BRNetworkParameters(path: "/home/all-modules")
|
||||
param.method = .get
|
||||
param.isToast = true
|
||||
|
||||
BRNetwork.request(parameters: param) { (response: BRNetworkResponse<BRListModel<BRHomeModuleItem>>) in
|
||||
completer?(response.data?.list)
|
||||
}
|
||||
}
|
||||
|
||||
///排行
|
||||
static func requestTop10List(completer: ((_ list: [BRShortModel]?) -> Void)?) {
|
||||
var param = BRNetworkParameters(path: "/getVisitTop")
|
||||
param.method = .get
|
||||
|
||||
BRNetwork.request(parameters: param) { (response: BRNetworkResponse<[BRShortModel]>) in
|
||||
completer?(response.data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
20
BeeReel/Base/Network/API/BRUserAPI.swift
Normal file
@ -0,0 +1,20 @@
|
||||
//
|
||||
// BRUserAPI.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/27.
|
||||
//
|
||||
|
||||
|
||||
class BRUserAPI {
|
||||
///获取用户信息
|
||||
static func requestUserInfo(completer: ((_ userInfo: BRUserInfo?) -> Void)?) {
|
||||
|
||||
var param = BRNetworkParameters(path: "/customer/info")
|
||||
param.method = .get
|
||||
|
||||
BRNetwork.request(parameters: param) { (response: BRNetworkResponse<BRUserInfo>) in
|
||||
completer?(response.data)
|
||||
}
|
||||
}
|
||||
}
|
75
BeeReel/Base/Network/API/BRVideoAPI.swift
Normal file
@ -0,0 +1,75 @@
|
||||
//
|
||||
// BRVideoAPI.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRVideoAPI {
|
||||
///获取视频详情
|
||||
static func requestVideoDetail(shortPlayId: String, activityId: String? = nil, revolution: BRShortModel.VideoRevolution? = nil, completer: ((_ model: BRVideoDetailModel?) -> Void)?) {
|
||||
var parameters: [String : Any] = [
|
||||
"short_play_id" : shortPlayId,
|
||||
"video_id" : "0"
|
||||
]
|
||||
|
||||
if let activityId = activityId {
|
||||
parameters["activity_id"] = activityId
|
||||
}
|
||||
|
||||
if let revolution = revolution?.rawValue {
|
||||
parameters["revolution"] = revolution
|
||||
}
|
||||
|
||||
var param = BRNetworkParameters(path: "/getVideoDetails")
|
||||
param.method = .get
|
||||
param.parameters = parameters
|
||||
|
||||
BRNetwork.request(parameters: param) { (response: BRNetworkResponse<BRVideoDetailModel>) in
|
||||
completer?(response.data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///收藏
|
||||
static func requestFavorite(isFavorite: Bool, shortPlayId: String, videoId: String?, isLoding: Bool = true, success: (() -> Void)?, failure: (() -> Void)? = nil) {
|
||||
let path: String
|
||||
if isFavorite {
|
||||
path = "/collect"
|
||||
} else {
|
||||
path = "/cancelCollect"
|
||||
}
|
||||
|
||||
var parameters: [String : Any] = [
|
||||
"short_play_id" : shortPlayId,
|
||||
]
|
||||
|
||||
if let videoId = videoId {
|
||||
parameters["video_id"] = videoId
|
||||
}
|
||||
|
||||
var param = BRNetworkParameters(path: path)
|
||||
param.isLoding = isLoding
|
||||
param.parameters = parameters
|
||||
|
||||
BRNetwork.request(parameters: param) { (response: BRNetworkResponse<String>) in
|
||||
if response.code == BRNetworkCodeSucceed {
|
||||
success?()
|
||||
NotificationCenter.default.post(name: BRVideoAPI.updateShortFavoriteStateNotification, object: nil, userInfo: [
|
||||
"state" : isFavorite,
|
||||
"id" : shortPlayId,
|
||||
])
|
||||
} else {
|
||||
failure?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension BRVideoAPI {
|
||||
///更新短剧关注状态 [ "state" : isFavorite, "id" : shortPlayId,]
|
||||
@objc static let updateShortFavoriteStateNotification = NSNotification.Name(rawValue: "BRVideoAPI.updateShortFavoriteStateNotification")
|
||||
}
|
102
BeeReel/Base/Network/Base/BRCryptorService.swift
Normal file
@ -0,0 +1,102 @@
|
||||
//
|
||||
// BRCryptorService.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
struct BRCryptorService {
|
||||
|
||||
// 定义常量
|
||||
static let EN_STR_TAG: String = "$" // 替换为实际的加密标记
|
||||
|
||||
// 解密字符串
|
||||
static func decrypt(data: String) -> String {
|
||||
guard data.hasPrefix(EN_STR_TAG) else {
|
||||
// fatalError("Invalid encoded string")
|
||||
return data
|
||||
}
|
||||
|
||||
let decryptedData = deStrBytes(data: data)
|
||||
return String(data: decryptedData, encoding: .utf8) ?? ""
|
||||
}
|
||||
|
||||
// 从十六进制字符串解密字节
|
||||
static func deStrBytes(data: String) -> Data {
|
||||
|
||||
let hexData = String(data.dropFirst())
|
||||
var bytes = Data()
|
||||
|
||||
var index = hexData.startIndex
|
||||
while index < hexData.endIndex {
|
||||
let nextIndex = hexData.index(index, offsetBy: 2, limitedBy: hexData.endIndex) ?? hexData.endIndex
|
||||
let byteString = String(hexData[index..<nextIndex])
|
||||
|
||||
if let byte = UInt8(byteString, radix: 16) {
|
||||
bytes.append(byte)
|
||||
}
|
||||
|
||||
index = nextIndex
|
||||
}
|
||||
|
||||
return de(data: bytes)
|
||||
}
|
||||
|
||||
// 解密数据
|
||||
static func de(data: Data) -> Data {
|
||||
guard !data.isEmpty else {
|
||||
return data
|
||||
}
|
||||
|
||||
let saltLen = Int(data[data.startIndex])
|
||||
guard data.count >= 1 + saltLen else {
|
||||
return data
|
||||
}
|
||||
|
||||
let salt = data.subdata(in: 1..<1+saltLen)
|
||||
let encryptedData = data.subdata(in: 1+saltLen..<data.count)
|
||||
|
||||
return deWithSalt(data: encryptedData, salt: salt)
|
||||
}
|
||||
|
||||
// 使用盐值解密数据
|
||||
static func deWithSalt(data: Data, salt: Data) -> Data {
|
||||
let decryptedData = cxEd(data: data)
|
||||
return removeSalt(data: decryptedData, salt: salt)
|
||||
}
|
||||
|
||||
// 加密/解密数据(按位取反)
|
||||
static func cxEd(data: Data) -> Data {
|
||||
return Data(data.map { $0 ^ 0xFF })
|
||||
}
|
||||
|
||||
// 从数据中移除盐值
|
||||
static func removeSalt(data: Data, salt: Data) -> Data {
|
||||
guard !salt.isEmpty else {
|
||||
return data
|
||||
}
|
||||
|
||||
var result = Data()
|
||||
let saltBytes = [UInt8](salt)
|
||||
let saltCount = saltBytes.count
|
||||
|
||||
for (index, byte) in data.enumerated() {
|
||||
let saltByte = saltBytes[index % saltCount]
|
||||
let decryptedByte = calRemoveSalt(v: byte, s: saltByte)
|
||||
result.append(decryptedByte)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 计算移除盐值后的字节
|
||||
static func calRemoveSalt(v: UInt8, s: UInt8) -> UInt8 {
|
||||
if v >= s {
|
||||
return v - s
|
||||
} else {
|
||||
return UInt8(0xFF) - (s - v) + 1
|
||||
}
|
||||
}
|
||||
}
|
229
BeeReel/Base/Network/Base/BRNetwork.swift
Normal file
@ -0,0 +1,229 @@
|
||||
//
|
||||
// BRNetwork.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Moya
|
||||
import SmartCodable
|
||||
|
||||
///获取数据成功
|
||||
let BRNetworkCodeSucceed = 200
|
||||
|
||||
|
||||
struct BRNetwork {
|
||||
///用来获取token的线程
|
||||
private static let operationQueue = OperationQueue()
|
||||
private static var tokenOperation: BlockOperation?
|
||||
|
||||
static let provider = MoyaProvider<BRNetworkTarget>(requestClosure: CustomApiTimeoutClosure)
|
||||
|
||||
static func request<T>(parameters: BRNetworkParameters, completion: ((_ response: BRNetworkResponse<T>) -> Void)?) {
|
||||
|
||||
if BRLoginManager.manager.token == nil {
|
||||
self.requestToken(completer: nil)
|
||||
}
|
||||
|
||||
|
||||
if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" {
|
||||
|
||||
let requestOperation = BlockOperation {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
_request(parameters: parameters) { (response: BRNetworkResponse<T>) in
|
||||
semaphore.signal()
|
||||
completion?(response)
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
///设置依赖关系
|
||||
requestOperation.addDependency(tokenOperation)
|
||||
|
||||
operationQueue.addOperation(requestOperation)
|
||||
} else {
|
||||
_request(parameters: parameters, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
static func _request<T>(parameters: BRNetworkParameters, completion: ((_ response: BRNetworkResponse<T>) -> Void)?) -> Cancellable {
|
||||
|
||||
if parameters.isLoding {
|
||||
BRHUD.show()
|
||||
}
|
||||
return provider.requestCustomJson(.request(parameters: parameters)) { (result) in
|
||||
|
||||
if parameters.isLoding {
|
||||
BRHUD.dismiss()
|
||||
}
|
||||
guard let completion = completion else {return}
|
||||
|
||||
|
||||
_resultDispose(parameters: parameters, result: result, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static func _resultDispose<T>(parameters: BRNetworkParameters, result: Result<Moya.Response, MoyaError>, completion: ((_ response: BRNetworkResponse<T>) -> Void)?) {
|
||||
|
||||
switch result {
|
||||
case .success(let response):
|
||||
let code = response.statusCode
|
||||
if code == 401 || code == 402 || code == 403 {
|
||||
|
||||
if parameters.path == "/customer/register" {
|
||||
var res = BRNetworkResponse<T>()
|
||||
res.code = -1
|
||||
if parameters.isToast {
|
||||
BRToast.show(text: "Error".localized)
|
||||
}
|
||||
completion?(res)
|
||||
} else {
|
||||
if code == 402, parameters.isToast {
|
||||
BRToast.show(text: "veloria_network_error_1".localized)
|
||||
}
|
||||
///重新获取token
|
||||
self.requestToken { token in
|
||||
if token != nil {
|
||||
BRLoginManager.manager.updateUserInfo(completer: nil)
|
||||
}
|
||||
}
|
||||
|
||||
///将请求失败数据重新请求
|
||||
if let tokenOperation = self.tokenOperation, parameters.path != "/customer/register" {
|
||||
|
||||
let requestOperation = BlockOperation {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
_request(parameters: parameters) { (response: BRNetworkResponse<T>) in
|
||||
semaphore.signal()
|
||||
completion?(response)
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
///设置依赖关系
|
||||
requestOperation.addDependency(tokenOperation)
|
||||
|
||||
operationQueue.addOperation(requestOperation)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let tempData = try response.mapString()
|
||||
brLog(message: parameters.parameters)
|
||||
brLog(message: parameters.path)
|
||||
|
||||
|
||||
DispatchQueue.global().async {
|
||||
let response: BRNetworkResponse<T> = _deserialize(data: tempData)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if response.code != BRNetworkCodeSucceed {
|
||||
if parameters.isToast {
|
||||
BRToast.show(text: response.msg)
|
||||
}
|
||||
}
|
||||
completion?(response)
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
var res = BRNetworkResponse<T>()
|
||||
res.code = -1
|
||||
if parameters.isToast {
|
||||
BRToast.show(text: "Error".localized)
|
||||
}
|
||||
completion?(res)
|
||||
}
|
||||
case .failure(let error):
|
||||
brLog(message: error)
|
||||
var res = BRNetworkResponse<T>()
|
||||
res.code = -1
|
||||
if parameters.isToast {
|
||||
BRToast.show(text: "veloria_network".localized)
|
||||
}
|
||||
completion?(res)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///解析数据
|
||||
static private func _deserialize<T>(data: String) -> BRNetworkResponse<T> {
|
||||
var response: BRNetworkResponse<T>?
|
||||
|
||||
let decrypted = BRCryptorService.decrypt(data: data)
|
||||
brLog(message: decrypted)
|
||||
// let option: SmartDecodingOption = .key(.fromSnakeCase)
|
||||
response = BRNetworkResponse<T>.deserialize(from: decrypted)
|
||||
response?.rawData = decrypted
|
||||
|
||||
if let response = response {
|
||||
return response
|
||||
} else {
|
||||
var response = BRNetworkResponse<T>()
|
||||
response.code = -1
|
||||
response.msg = "Error".localized
|
||||
return response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension BRNetwork {
|
||||
///获取token
|
||||
static func requestToken(completer: ((_ token: BRLoginToken?) -> Void)?) {
|
||||
guard self.tokenOperation == nil else {
|
||||
completer?(nil)
|
||||
return
|
||||
}
|
||||
|
||||
self.tokenOperation = BlockOperation(block: {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
let param = BRNetworkParameters(path: "/customer/register")
|
||||
BRNetwork.request(parameters: param) { (response: BRNetworkResponse<BRLoginToken>) in
|
||||
if let token = response.data {
|
||||
BRLoginManager.manager.setLoginToken(token: token)
|
||||
}
|
||||
do { semaphore.signal() }
|
||||
self.tokenOperation = nil
|
||||
completer?(response.data)
|
||||
}
|
||||
semaphore.wait()
|
||||
})
|
||||
operationQueue.addOperation(self.tokenOperation!)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension MoyaProvider {
|
||||
|
||||
@discardableResult
|
||||
func requestCustomJson(_ target: Target, callbackQueue: DispatchQueue? = nil, completion: Completion?) -> Cancellable {
|
||||
return request(target, callbackQueue: callbackQueue) { (result) in
|
||||
guard let completion = completion else {return}
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let CustomApiTimeoutClosure = {(endpoint: Endpoint, closure: MoyaProvider<BRNetworkTarget>.RequestResultClosure) -> Void in
|
||||
|
||||
if var urlRequest = try? endpoint.urlRequest() {
|
||||
///总是获取新数据
|
||||
urlRequest.cachePolicy = .reloadIgnoringCacheData
|
||||
urlRequest.timeoutInterval = 30
|
||||
closure(.success(urlRequest))
|
||||
} else {
|
||||
closure(.failure(MoyaError.requestMapping(endpoint.url)))
|
||||
}
|
||||
|
||||
#if DEBUG ///
|
||||
//print(try? endpoint.urlRequest() )
|
||||
#endif
|
||||
}
|
105
BeeReel/Base/Network/Base/BRNetworkTarget.swift
Normal file
@ -0,0 +1,105 @@
|
||||
//
|
||||
// BRNetworkTarget.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
|
||||
import Moya
|
||||
import SmartCodable
|
||||
|
||||
struct BRNetworkData<T: SmartCodable> {
|
||||
var parameters: BRNetworkParameters?
|
||||
var completion: ((_ response: BRNetworkResponse<T>) -> Void)?
|
||||
}
|
||||
|
||||
struct BRNetworkParameters {
|
||||
var baseURL: URL?
|
||||
var parameters: [String : Any]?
|
||||
var method: Moya.Method = .post
|
||||
var path: String
|
||||
var isLoding: Bool = false
|
||||
var isToast: Bool = true
|
||||
}
|
||||
|
||||
struct BRNetworkResponse<T: SmartCodable>: SmartCodable {
|
||||
var code: Int?
|
||||
var data: T?
|
||||
var msg: String?
|
||||
|
||||
///原始数据
|
||||
@IgnoredKey
|
||||
var rawData: Any?
|
||||
}
|
||||
|
||||
enum BRNetworkTarget {
|
||||
case request(parameters: BRNetworkParameters)
|
||||
}
|
||||
|
||||
extension BRNetworkTarget: TargetType {
|
||||
var baseURL: URL {
|
||||
return .init(string: BRBaseURL)!
|
||||
}
|
||||
|
||||
var path: String {
|
||||
switch self {
|
||||
case .request(let parameters):
|
||||
return BRURLPathPrefix + parameters.path
|
||||
}
|
||||
}
|
||||
|
||||
var method: Moya.Method {
|
||||
switch self {
|
||||
case .request(let parameters):
|
||||
return parameters.method
|
||||
}
|
||||
}
|
||||
|
||||
var task: Moya.Task {
|
||||
switch self {
|
||||
case .request(let parameters):
|
||||
let parameters = parameters.parameters ?? [:]
|
||||
return .requestParameters(parameters: parameters, encoding: getEncoding())
|
||||
}
|
||||
}
|
||||
|
||||
var headers: [String : String]? {
|
||||
let userToken = BRLoginManager.manager.token?.token ?? ""
|
||||
let dic: [String : String] = [
|
||||
"system-version" : kBROsVersion,
|
||||
"lang-key" : BRLocalizedManager.manager.currentLocalizedKey,//当前语言
|
||||
"time-zone" : self.timeZone(), //时区
|
||||
"app-version" : kBRAPPVersion,
|
||||
"device-id" : JXUUID.uuid(), //设备id
|
||||
"brand" : "apple", //品牌
|
||||
"app-name" : kBRAPPBundleIdentifier,
|
||||
"system-type" : "ios",
|
||||
"idfa" : JXUUID.idfa(),
|
||||
"model" : UIDevice.br_machineModelName(),
|
||||
"authorization" : userToken
|
||||
]
|
||||
return dic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRNetworkTarget {
|
||||
var sampleData: Data { return "".data(using: String.Encoding.utf8)! }
|
||||
|
||||
func getEncoding() -> ParameterEncoding {
|
||||
switch self.method {
|
||||
case .get, .delete:
|
||||
return URLEncoding.default
|
||||
default:
|
||||
return JSONEncoding.default
|
||||
}
|
||||
}
|
||||
|
||||
func timeZone() -> String {
|
||||
let timeZone = NSTimeZone.local as NSTimeZone
|
||||
let timeZoneSecondsFromGMT = timeZone.secondsFromGMT / 3600
|
||||
return String(format: "GMT+0%d:00", timeZoneSecondsFromGMT)
|
||||
}
|
||||
|
||||
}
|
39
BeeReel/Base/Network/Base/BRURLPath.swift
Normal file
@ -0,0 +1,39 @@
|
||||
//
|
||||
// BRURLPath.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
|
||||
|
||||
let BRBaseURL = "https://api-qjwl168.qjwl168.com"
|
||||
let BRURLPathPrefix = "/velo"
|
||||
|
||||
let BRWebBaseURL = "https://www.qjwl168.com"
|
||||
let BRCampaignWebURL = "https://campaign.qjwl168.com"
|
||||
|
||||
|
||||
///用户协议
|
||||
let kSBUserAgreementWebUrl = BRWebBaseURL + "/user_policy"
|
||||
///隐私协议
|
||||
let kSBPrivacyPolicyWebUrl = BRWebBaseURL + "/private"
|
||||
///儿童个人信息保护规则
|
||||
let kSBInformationProtectionWebUrl = BRWebBaseURL + "/information_protection"
|
||||
///第三方共享清单
|
||||
let kSBInformationSharingWebUrl = BRWebBaseURL + "/information_sharing"
|
||||
///收集个人信息明示清单
|
||||
let kSBPersoInforDisclosureWebUrl = BRWebBaseURL + "/persoInfor_disclosure"
|
||||
///全国青少年互联网文明公约
|
||||
let kSBCivizatioConventionWebUrl = BRWebBaseURL + "/civizatio_convention"
|
||||
///会员服务协议
|
||||
let kSBMemberShipAgreement = BRWebBaseURL + "/member_ship_agreement"
|
||||
|
||||
///反馈首页
|
||||
let kSBFeedBackHomeWebUrl = BRCampaignWebURL + "/pages/leave/index"
|
||||
///反馈列表
|
||||
let kSBFeedBackListWebUrl = BRCampaignWebURL + "/pages/leave/list"
|
||||
///反馈详情
|
||||
let kSBFeedBackDetailWebUrl = BRCampaignWebURL + "/pages/leave/detail"
|
||||
///活动页面
|
||||
let kSBRewardsWebUrl = BRCampaignWebURL + "/pages/reward/theme4"
|
22
BeeReel/Base/View/BRCollectionView.swift
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// BRCollectionView.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRCollectionView: UICollectionView {
|
||||
|
||||
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
|
||||
super.init(frame: frame, collectionViewLayout: layout)
|
||||
self.backgroundColor = .clear
|
||||
self.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
21
BeeReel/Base/View/BRCollectionViewCell.swift
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// BRCollectionViewCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.layer.rasterizationScale = UIScreen.main.scale
|
||||
self.layer.shouldRasterize = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
}
|
||||
}
|
77
BeeReel/Base/View/BRImageView.swift
Normal file
@ -0,0 +1,77 @@
|
||||
//
|
||||
// BRImageView.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRImageView: UIImageView {
|
||||
|
||||
var placeholderColor = UIColor.black
|
||||
|
||||
private lazy var placeholderImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "placeholder_image_01"))
|
||||
imageView.isHidden = true
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
return imageView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
}
|
||||
|
||||
override init(image: UIImage?) {
|
||||
super.init(image: image)
|
||||
_init()
|
||||
}
|
||||
|
||||
override init(image: UIImage?, highlightedImage: UIImage?) {
|
||||
super.init(image: image, highlightedImage: highlightedImage)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
func _init() {
|
||||
self.contentMode = .scaleAspectFill
|
||||
self.layer.masksToBounds = true
|
||||
if image == nil {
|
||||
self.backgroundColor = self.placeholderColor
|
||||
placeholderImageView.isHidden = false
|
||||
}
|
||||
|
||||
addSubview(placeholderImageView)
|
||||
}
|
||||
|
||||
override var image: UIImage? {
|
||||
didSet {
|
||||
if self.backgroundColor == nil && image == nil {
|
||||
self.backgroundColor = self.placeholderColor
|
||||
} else if image != nil {
|
||||
if self.backgroundColor == self.placeholderColor {
|
||||
self.backgroundColor = nil
|
||||
}
|
||||
}
|
||||
|
||||
if image == nil {
|
||||
placeholderImageView.isHidden = false
|
||||
} else {
|
||||
placeholderImageView.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
placeholderImageView.frame = .init(x: 0, y: 0, width: self.bounds.width * (2 / 3), height: self.bounds.height * (2 / 3))
|
||||
placeholderImageView.center = .init(x: self.bounds.width / 2, y: self.bounds.height / 2)
|
||||
}
|
||||
}
|
49
BeeReel/Base/View/BRTableView.swift
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// BRTableView.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRTableView: UITableView {
|
||||
|
||||
var insetGroupedMargins: CGFloat = 15
|
||||
|
||||
override init(frame: CGRect, style: UITableView.Style) {
|
||||
super.init(frame: frame, style: style)
|
||||
separatorColor = .colorFFFFFF(alpha: 0.1)
|
||||
separatorInset = .init(top: 0, left: 16, bottom: 0, right: 16)
|
||||
self.backgroundColor = .clear
|
||||
self.contentInsetAdjustmentBehavior = .never
|
||||
|
||||
if style == .insetGrouped {
|
||||
sectionFooterHeight = 12
|
||||
sectionHeaderHeight = 0.1
|
||||
} else if style == .plain {
|
||||
if #available(iOS 15.0, *) {
|
||||
sectionHeaderTopPadding = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
///修改 insetGrouped 的边距 参考https://github.com/QMUI/QMUIDemo_iOS/blob/master/QMUI/QMUIKit/UIKitExtensions/UITableView%2BQMUI.m
|
||||
override var layoutMargins: UIEdgeInsets {
|
||||
set {
|
||||
super.layoutMargins = newValue
|
||||
}
|
||||
get {
|
||||
var margins = super.layoutMargins
|
||||
if self.style == .insetGrouped {
|
||||
margins.left = self.safeAreaInsets.left + insetGroupedMargins
|
||||
margins.right = self.safeAreaInsets.right + insetGroupedMargins
|
||||
}
|
||||
return margins
|
||||
}
|
||||
}
|
||||
}
|
49
BeeReel/Base/View/BRTableViewCell.swift
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// BRTableViewCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRTableViewCell: UITableViewCell {
|
||||
|
||||
private(set) lazy var br_indicatorImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "arrow_right_icon_02"))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
self.layer.rasterizationScale = UIScreen.main.scale
|
||||
self.layer.shouldRasterize = true
|
||||
self.selectionStyle = .none
|
||||
self.backgroundColor = .clear
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
// Initialization code
|
||||
}
|
||||
|
||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||
super.setSelected(selected, animated: animated)
|
||||
|
||||
// Configure the view for the selected state
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension UITableViewCell {
|
||||
|
||||
var br_tableView: UITableView? {
|
||||
return self.value(forKey: "_tableView") as? UITableView
|
||||
}
|
||||
}
|
224
BeeReel/Base/View/TabBar/BRTabBar.swift
Normal file
@ -0,0 +1,224 @@
|
||||
//
|
||||
// BRTabBar.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRTabBar: UITabBar {
|
||||
|
||||
var containers = [BRTabBarItemContainer]()
|
||||
private let tagOffset = 1000
|
||||
|
||||
override func sizeThatFits(_ size: CGSize) -> CGSize {
|
||||
var size = super.sizeThatFits(size)
|
||||
size.height = UIScreen.customTabBarHeight
|
||||
return size
|
||||
}
|
||||
|
||||
private lazy var topImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "tabbar_top_icon"))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
override var items: [UITabBarItem]? {
|
||||
didSet {
|
||||
reload()
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.backgroundColor = .color1C1C1C()
|
||||
self.setRoundedCorner(topLeft: 30, topRight: 30, bottomLeft: 0, bottomRight: 0)
|
||||
|
||||
addSubview(topImageView)
|
||||
|
||||
topImageView.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview()
|
||||
make.centerX.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
// updateLayout()
|
||||
|
||||
}
|
||||
|
||||
override func setItems(_ items: [UITabBarItem]?, animated: Bool) {
|
||||
super.setItems(items, animated: animated)
|
||||
self.reload()
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
var b = super.point(inside: point, with: event)
|
||||
if !b {
|
||||
for container in containers {
|
||||
let p = CGPoint.init(x: point.x - container.frame.origin.x, y: point.y - container.frame.origin.y)
|
||||
if container.point(inside: p, with: event) {
|
||||
b = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
extension BRTabBar {
|
||||
|
||||
func updateLayout() {
|
||||
subviews.forEach {
|
||||
if let cls = NSClassFromString("UITabBarButton") {
|
||||
if $0.isKind(of: cls) == true {
|
||||
$0.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var index = 0
|
||||
let width = self.width / CGFloat(containers.count)
|
||||
let height = self.height
|
||||
var x = 0.0
|
||||
containers.forEach {
|
||||
if $0.contentView == (selectedItem as? BRTabBarItem)?.contentView {
|
||||
index = $0.tag - tagOffset
|
||||
}
|
||||
|
||||
$0.frame = .init(x: x, y: 0, width: width, height: height)
|
||||
x += width
|
||||
|
||||
}
|
||||
updateSelectedStatus(index: index, animated: false)
|
||||
|
||||
}
|
||||
|
||||
|
||||
///更新选中状态
|
||||
private func updateSelectedStatus(index: Int, animated: Bool) {
|
||||
self.bringSubviewToFront(self.topImageView)
|
||||
|
||||
var selectedView: UIView?
|
||||
containers.forEach {
|
||||
if $0.tag == index + tagOffset {
|
||||
selectedView = $0
|
||||
}
|
||||
}
|
||||
|
||||
if let view = selectedView {
|
||||
self.topImageView.snp.remakeConstraints { make in
|
||||
make.top.equalToSuperview()
|
||||
make.centerX.equalTo(view)
|
||||
}
|
||||
} else {
|
||||
self.topImageView.snp.removeConstraints()
|
||||
}
|
||||
|
||||
if animated {
|
||||
UIView.animate(withDuration: 0.3) {
|
||||
self.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension BRTabBar {
|
||||
|
||||
func removeAll() {
|
||||
for container in containers {
|
||||
container.removeFromSuperview()
|
||||
}
|
||||
containers.removeAll()
|
||||
}
|
||||
|
||||
func reload() {
|
||||
removeAll()
|
||||
|
||||
guard let items = self.items else { return }
|
||||
|
||||
|
||||
items.enumerated().forEach { (index, item) in
|
||||
let container = BRTabBarItemContainer(self, tag: index + tagOffset)
|
||||
addSubview(container)
|
||||
|
||||
if let item = item as? BRTabBarItem {
|
||||
container.contentView = item.contentView
|
||||
}
|
||||
containers.append(container)
|
||||
}
|
||||
updateLayout()
|
||||
|
||||
self.setNeedsLayout()
|
||||
|
||||
}
|
||||
|
||||
@objc func highlightAction(_ sender: AnyObject?) {
|
||||
guard let container = sender as? BRTabBarItemContainer else {
|
||||
return
|
||||
}
|
||||
let newIndex = max(0, container.tag - tagOffset)
|
||||
guard newIndex < items?.count ?? 0, let item = self.items?[newIndex], item.isEnabled == true else {
|
||||
return
|
||||
}
|
||||
|
||||
if let item = item as? BRTabBarItem {
|
||||
item.contentView.highlight(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func dehighlightAction(_ sender: AnyObject?) {
|
||||
guard let container = sender as? BRTabBarItemContainer else {
|
||||
return
|
||||
}
|
||||
let newIndex = max(0, container.tag - tagOffset)
|
||||
guard newIndex < items?.count ?? 0, let item = self.items?[newIndex], item.isEnabled == true else {
|
||||
return
|
||||
}
|
||||
|
||||
if let item = item as? BRTabBarItem {
|
||||
item.contentView.dehighlight(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func selectAction(_ sender: AnyObject?) {
|
||||
guard let container = sender as? BRTabBarItemContainer else {
|
||||
return
|
||||
}
|
||||
select(itemAtIndex: container.tag - tagOffset, animated: true)
|
||||
}
|
||||
|
||||
@objc func select(itemAtIndex idx: Int, animated: Bool) {
|
||||
let newIndex = max(0, idx)
|
||||
let currentIndex = (selectedItem != nil) ? (items?.firstIndex(of: selectedItem!) ?? -1) : -1
|
||||
guard newIndex < items?.count ?? 0, let item = self.items?[newIndex], item.isEnabled == true else {
|
||||
return
|
||||
}
|
||||
|
||||
if currentIndex != newIndex {
|
||||
if currentIndex != -1 && currentIndex < items?.count ?? 0{
|
||||
if let currentItem = items?[currentIndex] as? BRTabBarItem {
|
||||
currentItem.contentView.deselect(animated: animated, completion: nil)
|
||||
}
|
||||
}
|
||||
if let item = item as? BRTabBarItem {
|
||||
item.contentView.select(animated: animated, completion: nil)
|
||||
}
|
||||
} else if currentIndex == newIndex {
|
||||
if let item = item as? BRTabBarItem {
|
||||
item.contentView.reselect(animated: animated, completion: nil)
|
||||
}
|
||||
}
|
||||
updateSelectedStatus(index: newIndex, animated: animated)
|
||||
|
||||
delegate?.tabBar?(self, didSelect: item)
|
||||
}
|
||||
}
|
45
BeeReel/Base/View/TabBar/BRTabBarItem.swift
Normal file
@ -0,0 +1,45 @@
|
||||
//
|
||||
// BRTabBarItem.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRTabBarItem: UITabBarItem {
|
||||
|
||||
private(set) var contentView: BRTabBarItemContentView
|
||||
|
||||
|
||||
override var title: String? {
|
||||
didSet {
|
||||
contentView.title = title
|
||||
}
|
||||
}
|
||||
override var image: UIImage? {
|
||||
didSet {
|
||||
contentView.image = image
|
||||
}
|
||||
}
|
||||
|
||||
override var selectedImage: UIImage? {
|
||||
didSet {
|
||||
contentView.selectedImage = selectedImage
|
||||
}
|
||||
}
|
||||
|
||||
public init(_ contentView: BRTabBarItemContentView = BRTabBarItemContentView(), title: String? = nil, image: UIImage? = nil, selectedImage: UIImage? = nil, tag: Int = 0) {
|
||||
self.contentView = contentView
|
||||
super.init()
|
||||
|
||||
self.title = title
|
||||
self.image = image
|
||||
self.selectedImage = selectedImage
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
56
BeeReel/Base/View/TabBar/BRTabBarItemContainer.swift
Normal file
@ -0,0 +1,56 @@
|
||||
//
|
||||
// BRTabBarItemContainer.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRTabBarItemContainer: UIControl {
|
||||
|
||||
var contentView: BRTabBarItemContentView? {
|
||||
didSet {
|
||||
oldValue?.removeFromSuperview()
|
||||
|
||||
if let contentView = contentView {
|
||||
addSubview(contentView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal init(_ target: AnyObject?, tag: Int) {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.tag = tag
|
||||
self.addTarget(target, action: #selector(BRTabBar.selectAction(_:)), for: .touchUpInside)
|
||||
self.addTarget(target, action: #selector(BRTabBar.highlightAction(_:)), for: .touchDown)
|
||||
self.addTarget(target, action: #selector(BRTabBar.highlightAction(_:)), for: .touchDragEnter)
|
||||
self.addTarget(target, action: #selector(BRTabBar.dehighlightAction(_:)), for: .touchDragExit)
|
||||
self.backgroundColor = .clear
|
||||
// self.isAccessibilityElement = true
|
||||
}
|
||||
|
||||
internal required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
internal override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
if let contentView = contentView {
|
||||
contentView.frame = self.bounds
|
||||
// contentView.updateLayout()
|
||||
}
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
var b = super.point(inside: point, with: event)
|
||||
if !b {
|
||||
for subview in self.subviews {
|
||||
if subview.point(inside: CGPoint.init(x: point.x - subview.frame.origin.x, y: point.y - subview.frame.origin.y), with: event) {
|
||||
b = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
}
|
161
BeeReel/Base/View/TabBar/BRTabBarItemContentView.swift
Normal file
@ -0,0 +1,161 @@
|
||||
//
|
||||
// BRTabBarItemContentView.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRTabBarItemContentView: UIView {
|
||||
|
||||
/// 是否被选中
|
||||
open var selected = false
|
||||
|
||||
/// 是否处于高亮状态
|
||||
open var highlighted = false
|
||||
|
||||
/// 是否支持高亮
|
||||
open var highlightEnabled = true
|
||||
|
||||
var title: String? {
|
||||
didSet {
|
||||
// selectedView.title = title
|
||||
}
|
||||
}
|
||||
var image: UIImage? {
|
||||
didSet {
|
||||
// normalView.image = image
|
||||
updateLayout()
|
||||
}
|
||||
}
|
||||
|
||||
var selectedImage: UIImage? {
|
||||
didSet {
|
||||
// selectedView.image = selectedImage
|
||||
updateLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var imageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
return imageView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.isUserInteractionEnabled = false
|
||||
self.layer.masksToBounds = true
|
||||
|
||||
addSubview(imageView)
|
||||
|
||||
|
||||
imageView.snp.makeConstraints { make in
|
||||
make.centerX.equalToSuperview()
|
||||
make.bottom.equalToSuperview().offset(-(UIScreen.tabbarSafeBottomMargin + 2))
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
open func updateDisplay() {
|
||||
|
||||
}
|
||||
|
||||
func updateLayout() {
|
||||
imageView.image = selected ? selectedImage : image
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension BRTabBarItemContentView {
|
||||
// MARK: - INTERNAL METHODS
|
||||
internal final func select(animated: Bool, completion: (() -> ())?) {
|
||||
selected = true
|
||||
if highlightEnabled && highlighted {
|
||||
highlighted = false
|
||||
dehighlightAnimation(animated: animated, completion: { [weak self] in
|
||||
self?.updateDisplay()
|
||||
self?.selectAnimation(animated: animated, completion: completion)
|
||||
})
|
||||
} else {
|
||||
updateDisplay()
|
||||
selectAnimation(animated: animated, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
internal final func deselect(animated: Bool, completion: (() -> ())?) {
|
||||
selected = false
|
||||
updateDisplay()
|
||||
self.deselectAnimation(animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
internal final func reselect(animated: Bool, completion: (() -> ())?) {
|
||||
if selected == false {
|
||||
select(animated: animated, completion: completion)
|
||||
} else {
|
||||
if highlightEnabled && highlighted {
|
||||
highlighted = false
|
||||
dehighlightAnimation(animated: animated, completion: { [weak self] in
|
||||
self?.reselectAnimation(animated: animated, completion: completion)
|
||||
})
|
||||
} else {
|
||||
reselectAnimation(animated: animated, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal final func highlight(animated: Bool, completion: (() -> ())?) {
|
||||
if !highlightEnabled {
|
||||
return
|
||||
}
|
||||
if highlighted == true {
|
||||
return
|
||||
}
|
||||
highlighted = true
|
||||
self.highlightAnimation(animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
internal final func dehighlight(animated: Bool, completion: (() -> ())?) {
|
||||
if !highlightEnabled {
|
||||
return
|
||||
}
|
||||
if !highlighted {
|
||||
return
|
||||
}
|
||||
highlighted = false
|
||||
self.dehighlightAnimation(animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
internal func badgeChanged(animated: Bool, completion: (() -> ())?) {
|
||||
self.badgeChangedAnimation(animated: animated, completion: completion)
|
||||
}
|
||||
|
||||
// MARK: - ANIMATION METHODS
|
||||
func selectAnimation(animated: Bool, completion: (() -> ())?) {
|
||||
self.imageView.image = self.selectedImage
|
||||
|
||||
}
|
||||
|
||||
func deselectAnimation(animated: Bool, completion: (() -> ())?) {
|
||||
self.imageView.image = self.image
|
||||
}
|
||||
|
||||
func reselectAnimation(animated: Bool, completion: (() -> ())?) {
|
||||
completion?()
|
||||
}
|
||||
|
||||
func highlightAnimation(animated: Bool, completion: (() -> ())?) {
|
||||
completion?()
|
||||
}
|
||||
|
||||
func dehighlightAnimation(animated: Bool, completion: (() -> ())?) {
|
||||
completion?()
|
||||
}
|
||||
|
||||
func badgeChangedAnimation(animated: Bool, completion: (() -> ())?) {
|
||||
completion?()
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
//
|
||||
// BRHomeTop10ViewController.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRHomeTop10ViewController: BRViewController {
|
||||
|
||||
|
||||
private lazy var dataArr: [BRShortModel] = []
|
||||
|
||||
|
||||
// private lazy var collectionView: BRCollectionView = {
|
||||
// let collectionView = BRCollectionView(frame: .zero, collectionViewLayout: <#T##UICollectionViewLayout#>)
|
||||
// }()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
requestDataArr()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension BRHomeTop10ViewController {
|
||||
|
||||
private func requestDataArr() {
|
||||
|
||||
BRHomeAPI.requestTop10List { [weak self] list in
|
||||
guard let self = self else { return }
|
||||
guard let list = list else { return }
|
||||
self.dataArr = list
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
239
BeeReel/Class/Home/Controller/BRHomeViewController.swift
Normal file
@ -0,0 +1,239 @@
|
||||
//
|
||||
// BRHomeViewController.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRHomeViewController: BRViewController {
|
||||
|
||||
|
||||
private lazy var viewModel = BRHomeViewModel()
|
||||
|
||||
|
||||
private lazy var spotlightVC: BRSpotlightViewViewController = {
|
||||
let vc = BRSpotlightViewViewController()
|
||||
vc.viewModel = self.viewModel
|
||||
return vc
|
||||
}()
|
||||
|
||||
private lazy var vcArr: [BRViewController] = {
|
||||
return [
|
||||
spotlightVC,
|
||||
BRHomeTop10ViewController(),
|
||||
BRViewController(),
|
||||
BRViewController(),
|
||||
BRViewController()
|
||||
]
|
||||
}()
|
||||
|
||||
private lazy var pageParam: WMZPageParam = {
|
||||
let param = WMZPageParam()
|
||||
param.wTitleArr = [
|
||||
"Spotlight".localized,
|
||||
"Top 10".localized,
|
||||
"Popular Picks".localized,
|
||||
"New Releases".localized,
|
||||
"Categories".localized
|
||||
]
|
||||
param.wViewController = { [weak self] index in
|
||||
return self?.vcArr[index]
|
||||
}
|
||||
param.wMenuHeadView = { [weak self] in
|
||||
if let bannerArr = self?.viewModel.bannerArr, bannerArr.count > 0 {
|
||||
return self?.headerView
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
//顶部可下拉
|
||||
param.wBounces = true
|
||||
param.wMenuTitleColor = .colorFFFFFF()
|
||||
param.wMenuTitleSelectColor = .colorFFFFFF()
|
||||
param.wMenuTitleUIFont = .fontMedium(ofSize: 12)
|
||||
param.wMenuTitleSelectUIFont = .fontMedium(ofSize: 12)
|
||||
param.wMenuTitleRadios = 17
|
||||
param.wMenuHeight = 34
|
||||
param.wMenuTitleOffset = 10
|
||||
param.wMenuInsets = UIEdgeInsets(top: 40, left: 59, bottom: 0, right: 15)
|
||||
param.wMenuBgColor = .clear
|
||||
param.wBgColor = .clear
|
||||
|
||||
|
||||
param.wTopSuspension = true
|
||||
param.wCustomDataViewTopOffset = UIScreen.statusBarHeight
|
||||
|
||||
param.wCustomMenuTitle = { [weak self] buttons in
|
||||
buttons?.forEach({ button in
|
||||
self?.setMenuTitle(button: button)
|
||||
})
|
||||
}
|
||||
|
||||
param.wCustomNaviBarY = { _ in
|
||||
return 0
|
||||
}
|
||||
param.wCustomTabbarY = { _ in
|
||||
return UIScreen.customTabBarHeight
|
||||
}
|
||||
|
||||
|
||||
param.wEventChildVCDidSroll = { [weak self] (pageVC, oldPoint, newPonit, currentScrollView) in
|
||||
guard let self = self else { return }
|
||||
|
||||
let top = max(0, newPonit.y)
|
||||
self.bgImageView.snp.updateConstraints { make in
|
||||
make.top.equalToSuperview().offset(-top)
|
||||
}
|
||||
self.updateStatusBarStyle()
|
||||
}
|
||||
|
||||
return param
|
||||
}()
|
||||
|
||||
private lazy var pageView: WMZPageView = {
|
||||
let view = WMZPageView(frame: self.view.bounds, autoFix: true, param: pageParam, parentReponder: self)
|
||||
view.backgroundColor = .clear
|
||||
view.downSc?.backgroundColor = .clear
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var bgImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "home_header_bg_image"))
|
||||
imageView.isHidden = true
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontMedium(ofSize: 17)
|
||||
label.textColor = .color1C1C1C()
|
||||
label.text = "Browse Genres".localized
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var headerView: BRHomeHeaderView = {
|
||||
let view = BRHomeHeaderView()
|
||||
view.frame = .init(x: 0, y: 0, width: UIScreen.width, height: view.contentHeight)
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var menuLeftView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = self.view.backgroundColor
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var searchButton: UIButton = {
|
||||
let button = UIButton(type: .custom)
|
||||
button.setImage(UIImage(named: "search_button_01"), for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.edgesForExtendedLayout = [.top, .bottom]
|
||||
self.statusBarStyle = .lightContent
|
||||
self.navigationController?.isNavigationBarHidden = true
|
||||
|
||||
br_setupUI()
|
||||
|
||||
requestHomeData()
|
||||
|
||||
setupPageView()
|
||||
|
||||
updateStatusBarStyle()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
self.navigationController?.setNavigationBarHidden(true, animated: true)
|
||||
}
|
||||
|
||||
|
||||
private func updateStatusBarStyle() {
|
||||
let point = self.pageView.downSc?.contentOffset ?? .zero
|
||||
|
||||
if point.y < 300, let bannerArr = self.viewModel.bannerArr, bannerArr.count > 0 {
|
||||
self.statusBarStyle = .lightContent
|
||||
} else {
|
||||
self.statusBarStyle = .darkContent
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension BRHomeViewController {
|
||||
|
||||
private func br_setupUI() {
|
||||
view.addSubview(bgImageView)
|
||||
view.addSubview(pageView)
|
||||
menuLeftView.addSubview(searchButton)
|
||||
|
||||
bgImageView.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview().offset(0)
|
||||
make.left.right.equalToSuperview()
|
||||
make.height.equalTo(UIScreen.statusBarHeight + 300)
|
||||
}
|
||||
|
||||
searchButton.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(15)
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
///自定义菜单样式
|
||||
private func setMenuTitle(button: UIButton) {
|
||||
button.setBackgroundImage(UIImage(named: "menu_bg_image_01"), for: .selected)
|
||||
button.setBackgroundImage(UIImage(color: .colorD3D3D3()), for: .normal)
|
||||
button.layer.masksToBounds = true
|
||||
}
|
||||
|
||||
private func setupPageView() {
|
||||
|
||||
self.pageView.upSc.addSubview(titleLabel)
|
||||
self.pageView.upSc.addSubview(menuLeftView)
|
||||
|
||||
// self.pageView.upSc.sendSubviewToBack(menuBgView)
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(15)
|
||||
make.top.equalToSuperview().offset(10)
|
||||
}
|
||||
|
||||
menuLeftView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview()
|
||||
make.top.equalToSuperview().offset(pageParam.wMenuInsets.top)
|
||||
make.height.equalTo(34)
|
||||
make.width.equalTo(59)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRHomeViewController {
|
||||
|
||||
private func requestHomeData() {
|
||||
|
||||
BRHomeAPI.requestHomeData { [weak self] list in
|
||||
guard let self = self else { return }
|
||||
self.viewModel.homeOldDataArr = list ?? []
|
||||
|
||||
self.headerView.bannerArr = self.viewModel.bannerArr
|
||||
if let bannerArr = self.viewModel.bannerArr, bannerArr.count > 0 {
|
||||
self.bgImageView.isHidden = false
|
||||
self.pageView.updateHeadView()
|
||||
} else {
|
||||
self.bgImageView.isHidden = true
|
||||
}
|
||||
self.spotlightVC.reloadData()
|
||||
self.updateStatusBarStyle()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,71 @@
|
||||
//
|
||||
// BRSpotlightViewViewController.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRSpotlightViewViewController: BRViewController, WMZPageProtocol {
|
||||
|
||||
var viewModel: BRHomeViewModel?
|
||||
|
||||
private lazy var tableView: BRTableView = {
|
||||
let tableView = BRTableView(frame: .zero, style: .plain)
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
tableView.register(BRSpotlightMainBaseCell.self, forCellReuseIdentifier: "cell")
|
||||
tableView.register(BRSpotlightHotMainCell.self, forCellReuseIdentifier: BRHomeModuleItem.ModuleKey.v3_recommand.rawValue)
|
||||
tableView.register(BRSpotlightTopMainCell.self, forCellReuseIdentifier: BRHomeModuleItem.ModuleKey.week_ranking.rawValue)
|
||||
tableView.register(BRSpotlightNewMainCell.self, forCellReuseIdentifier: BRHomeModuleItem.ModuleKey.new_recommand.rawValue)
|
||||
tableView.register(BRSpotlightRecommandMainCell.self, forCellReuseIdentifier: BRHomeModuleItem.ModuleKey.cagetory_recommand.rawValue)
|
||||
return tableView
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
br_setupUI()
|
||||
}
|
||||
|
||||
|
||||
func reloadData() {
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
func getMyScrollView() -> UIScrollView {
|
||||
return tableView
|
||||
}
|
||||
}
|
||||
|
||||
extension BRSpotlightViewViewController {
|
||||
|
||||
private func br_setupUI() {
|
||||
view.addSubview(tableView)
|
||||
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
make.top.equalToSuperview().offset(15)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- UITableViewDelegate UITableViewDataSource --------------
|
||||
extension BRSpotlightViewViewController: UITableViewDelegate, UITableViewDataSource {
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let moduleItem = self.viewModel?.spotlightDataArr[indexPath.row]
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: moduleItem?.module_key?.rawValue ?? "cell", for: indexPath) as! BRSpotlightMainBaseCell
|
||||
cell.moduleItem = moduleItem
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return self.viewModel?.spotlightDataArr.count ?? 0
|
||||
}
|
||||
|
||||
|
||||
}
|
67
BeeReel/Class/Home/Model/BRHomeModuleItem.swift
Normal file
@ -0,0 +1,67 @@
|
||||
//
|
||||
// BRHomeModuleItem.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/26.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SmartCodable
|
||||
|
||||
class BRHomeModuleItem: BRModel, SmartCodable {
|
||||
|
||||
enum ModuleKey: String, SmartCaseDefaultable {
|
||||
case banner = "home_banner"
|
||||
case v3_recommand = "home_v3_recommand"
|
||||
///分类推荐
|
||||
case cagetory_recommand = "home_cagetory_recommand"
|
||||
case week_ranking = "week_ranking"
|
||||
///跑马灯
|
||||
case marquee = "marquee"
|
||||
|
||||
case new_recommand = "new_recommand"
|
||||
}
|
||||
|
||||
|
||||
var module_key: ModuleKey?
|
||||
var title: String?
|
||||
var list: [BRShortModel]?
|
||||
// var categoryList: [VPCategoryModel]?
|
||||
|
||||
@SmartAny
|
||||
var data: Any?
|
||||
|
||||
@IgnoredKey
|
||||
var iconImage: UIImage?
|
||||
|
||||
@IgnoredKey
|
||||
var br_cellHeight: CGFloat?
|
||||
|
||||
|
||||
|
||||
func didFinishMapping() {
|
||||
if let data = data as? [[String : Any]] {
|
||||
// if module_key == .category_navigation {
|
||||
// self.categoryList = [VPCategoryModel].deserialize(from: data)
|
||||
// } else {
|
||||
// }
|
||||
self.list = [BRShortModel].deserialize(from: data)
|
||||
} else if let data = data as? [String : Any] {
|
||||
var dataList: [[String : Any]]?
|
||||
if let list = data["list"] as? [[String : Any]] {
|
||||
self.title = data["title"] as? String
|
||||
dataList = list
|
||||
|
||||
} else if let list = data["shortPlayList"] as? [[String : Any]] {
|
||||
self.title = data["category_name"] as? String
|
||||
dataList = list
|
||||
}
|
||||
|
||||
if let dataList = dataList {
|
||||
self.list = [BRShortModel].deserialize(from: dataList)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
61
BeeReel/Class/Home/Model/BRPagerViewTransformer.swift
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// BRPagerViewTransformer.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import FSPagerView
|
||||
|
||||
class BRPagerViewTransformer: FSPagerViewTransformer {
|
||||
|
||||
|
||||
override func applyTransform(to attributes: FSPagerViewLayoutAttributes) {
|
||||
guard let pagerView = self.pagerView else {
|
||||
return
|
||||
}
|
||||
|
||||
let position = attributes.position
|
||||
let scrollDirection = pagerView.scrollDirection
|
||||
let itemSpacing = (scrollDirection == .horizontal ? attributes.bounds.width : attributes.bounds.height) + self.proposedInteritemSpacing()
|
||||
|
||||
if self.type == .ferrisWheel {
|
||||
guard scrollDirection == .horizontal else {
|
||||
// This type doesn't support vertical mode
|
||||
return
|
||||
}
|
||||
// http://ronnqvi.st/translate-rotate-translate/
|
||||
var zIndex = 0
|
||||
var transform = CGAffineTransform.identity
|
||||
switch position {
|
||||
case -5 ... 5:
|
||||
let itemSpacing = attributes.bounds.width+self.proposedInteritemSpacing()
|
||||
let count: CGFloat = 40
|
||||
let circle: CGFloat = .pi * 2.0
|
||||
let radius = itemSpacing * count / circle
|
||||
let ty = radius * (self.type == .ferrisWheel ? 1 : -1)
|
||||
let theta = circle / count
|
||||
let rotation = position * theta * (self.type == .ferrisWheel ? 1 : -1)
|
||||
transform = transform.translatedBy(x: -position*itemSpacing, y: ty)
|
||||
transform = transform.rotated(by: rotation)
|
||||
transform = transform.translatedBy(x: 0, y: -ty)
|
||||
zIndex = Int((4.0-abs(position)*10))
|
||||
default:
|
||||
break
|
||||
}
|
||||
// attributes.alpha = abs(position) < 0.5 ? 1 : self.minimumAlpha
|
||||
|
||||
attributes.transform = transform
|
||||
attributes.zIndex = zIndex
|
||||
|
||||
|
||||
} else {
|
||||
super.applyTransform(to: attributes)
|
||||
}
|
||||
}
|
||||
|
||||
override func proposedInteritemSpacing() -> CGFloat {
|
||||
return pagerView?.interitemSpacing ?? 0
|
||||
}
|
||||
}
|
82
BeeReel/Class/Home/Model/BRShortModel.swift
Normal file
@ -0,0 +1,82 @@
|
||||
//
|
||||
// BRShortModel.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/26.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SmartCodable
|
||||
|
||||
class BRShortModel: BRModel, SmartCodable {
|
||||
|
||||
enum VideoRevolution: String, SmartCaseDefaultable {
|
||||
case r_540 = "540"
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
enum TagType: String, SmartCaseDefaultable {
|
||||
case hot = "hot"
|
||||
case new = "new"
|
||||
}
|
||||
|
||||
var id: String?
|
||||
var all_coins: String?
|
||||
var buy_type: String?
|
||||
var collect_total: Int?
|
||||
var vp_description: String?
|
||||
var episode_total: Int?
|
||||
var horizontally_img: String?
|
||||
var image_url: String?
|
||||
var is_collect: Bool?
|
||||
var name: String?
|
||||
var process: String?
|
||||
var search_click_total: String?
|
||||
var short_id: String?
|
||||
var short_play_id: String?
|
||||
var short_play_video_id: String?
|
||||
var tag_type: TagType?
|
||||
var video_info: BRVideoInfoModel?
|
||||
var category: [String]?
|
||||
///观看数
|
||||
var watch_total: Int?
|
||||
var current_episode: String?
|
||||
var video_url: String?
|
||||
var updated_at: String?
|
||||
|
||||
var revolution: VideoRevolution?
|
||||
|
||||
@IgnoredKey
|
||||
var titleAttributedString: NSAttributedString?
|
||||
@IgnoredKey
|
||||
var vp_isSelected: Bool?
|
||||
|
||||
|
||||
static func mappingForKey() -> [SmartKeyTransformer]? {
|
||||
return [
|
||||
CodingKeys.vp_description <--- ["description", "short_video_description"],
|
||||
CodingKeys.name <--- ["short_video_title", "name"]
|
||||
]
|
||||
}
|
||||
}
|
29
BeeReel/Class/Home/Model/BRVideoInfoModel.swift
Normal file
@ -0,0 +1,29 @@
|
||||
//
|
||||
// BRVideoInfoModel.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/26.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SmartCodable
|
||||
|
||||
class BRVideoInfoModel: BRModel, SmartCodable {
|
||||
|
||||
var coins: Int?
|
||||
var vip_coins: Int?
|
||||
var episode: String?
|
||||
var id: String?
|
||||
var image_url: String?
|
||||
///1:会员,2: 非会员
|
||||
var is_vip: Int?
|
||||
///是否锁定,购买后解锁
|
||||
var is_lock: Bool?
|
||||
var promise_view_ad: Int?
|
||||
var short_id: String?
|
||||
var short_play_id: String?
|
||||
var short_play_video_id: String?
|
||||
var video_url: String?
|
||||
///播放进度,毫秒
|
||||
var play_seconds: Int?
|
||||
}
|
126
BeeReel/Class/Home/View/BRHomeHeaderBannerCell.swift
Normal file
@ -0,0 +1,126 @@
|
||||
//
|
||||
// BRHomeHeaderBannerCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRHomeHeaderBannerCell: BRCollectionViewCell {
|
||||
|
||||
var model: BRShortModel? {
|
||||
didSet {
|
||||
coverImageView.br_setImage(url: model?.image_url)
|
||||
titleLabel.text = model?.name
|
||||
categoryLabel.text = model?.category?.first
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var coverImageView: BRImageView = {
|
||||
let imageView = BRImageView()
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var holeLayer: CAShapeLayer = {
|
||||
let maskLayer = CAShapeLayer()
|
||||
maskLayer.fillRule = .evenOdd
|
||||
return maskLayer
|
||||
}()
|
||||
|
||||
private lazy var playImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "play_icon_01"))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var bottomView: UIView = {
|
||||
let view = UIView()
|
||||
view.br_addEffectView()
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontBold(ofSize: 14)
|
||||
label.textColor = .colorFFFFFF()
|
||||
label.layer.shadowColor = UIColor.color000000(alpha: 0.25).cgColor
|
||||
label.layer.shadowOpacity = 1
|
||||
label.layer.shadowRadius = 1
|
||||
label.layer.shadowOffset = .init(width: 0, height: 1)
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var categoryLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontMedium(ofSize: 10)
|
||||
label.textColor = .colorF1FF94()
|
||||
return label
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
contentView.layer.cornerRadius = 16
|
||||
contentView.layer.masksToBounds = true
|
||||
contentView.layer.mask = self.holeLayer
|
||||
|
||||
br_setupUI()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
let holeCenter = CGPoint(x: 21, y: 21)
|
||||
let holeRadius: CGFloat = 11
|
||||
|
||||
let holePath = UIBezierPath(arcCenter: holeCenter, radius: holeRadius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
|
||||
|
||||
let path = UIBezierPath(rect: self.bounds)
|
||||
path.append(holePath)
|
||||
path.usesEvenOddFillRule = true
|
||||
|
||||
holeLayer.path = path.cgPath
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRHomeHeaderBannerCell {
|
||||
|
||||
private func br_setupUI() {
|
||||
contentView.addSubview(coverImageView)
|
||||
contentView.addSubview(playImageView)
|
||||
contentView.addSubview(bottomView)
|
||||
bottomView.addSubview(titleLabel)
|
||||
bottomView.addSubview(categoryLabel)
|
||||
|
||||
coverImageView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
playImageView.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
}
|
||||
|
||||
bottomView.snp.makeConstraints { make in
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
make.height.equalTo(50)
|
||||
}
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(8)
|
||||
make.top.equalToSuperview().offset(6)
|
||||
make.right.lessThanOrEqualToSuperview().offset(-8)
|
||||
}
|
||||
|
||||
categoryLabel.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(8)
|
||||
make.bottom.equalToSuperview().offset(-11)
|
||||
make.right.lessThanOrEqualToSuperview().offset(-8)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
86
BeeReel/Class/Home/View/BRHomeHeaderView.swift
Normal file
@ -0,0 +1,86 @@
|
||||
//
|
||||
// BRHomeHeaderView.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/25.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRHomeHeaderView: UIView {
|
||||
|
||||
var bannerArr: [BRShortModel]? {
|
||||
didSet {
|
||||
bannerParam.wData = bannerArr ?? []
|
||||
bannerView.updateUI()
|
||||
}
|
||||
}
|
||||
|
||||
var contentHeight: CGFloat = UIScreen.statusBarHeight + 107 + 280
|
||||
|
||||
private lazy var bgIconImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "home_header_icon_01"))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var bannerParam: WMZBannerParam = {
|
||||
let param = WMZBannerParam()
|
||||
param.wFrame = .init(x: 0, y: UIScreen.statusBarHeight + 107, width: UIScreen.width, height: 280)
|
||||
param.wItemSize = .init(width: 180, height: 230)
|
||||
param.wMyCell = { [weak self] (indexPath, collectionView, model, bgImageView, dataArr) in
|
||||
let cell = collectionView?.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath ?? .init(row: 0, section: 0)) as? BRHomeHeaderBannerCell
|
||||
cell?.model = model as? BRShortModel
|
||||
return cell
|
||||
}
|
||||
param.wEventClick = { [weak self] (anyId, index) in
|
||||
guard let self = self else { return }
|
||||
guard let model = anyId as? BRShortModel else { return }
|
||||
|
||||
let vc = BRVideoDetailViewController()
|
||||
vc.shortPlayId = model.short_play_id
|
||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
param.wSectionInset = .init(top: 0, left: 27, bottom: 0, right: 27)
|
||||
param.wCardOverLapCount = 4
|
||||
param.wLineSpacing = 40
|
||||
param.wCardOverLap = true
|
||||
param.wEffect = false
|
||||
param.wAutoScroll = false
|
||||
///开启无限循环
|
||||
param.wRepeat = true
|
||||
param.wHideBannerControl = true
|
||||
param.wCanFingerSliding = false
|
||||
|
||||
return param
|
||||
}()
|
||||
|
||||
private lazy var bannerView: WMZBannerView = {
|
||||
let view = WMZBannerView(configureWithModel: bannerParam)
|
||||
view.register(BRHomeHeaderBannerCell.self, forCellWithReuseIdentifier: "cell")
|
||||
return view
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
br_setupUI()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRHomeHeaderView {
|
||||
|
||||
private func br_setupUI() {
|
||||
addSubview(bannerView)
|
||||
addSubview(bgIconImageView)
|
||||
|
||||
bgIconImageView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(0)
|
||||
make.top.equalToSuperview().offset(UIScreen.statusBarHeight)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
163
BeeReel/Class/Home/View/Spotlight/BRSpotlightHotCell.swift
Normal file
@ -0,0 +1,163 @@
|
||||
//
|
||||
// BRSpotlightHotCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRSpotlightHotCell: BRCollectionViewCell {
|
||||
|
||||
var model: BRShortModel? {
|
||||
didSet {
|
||||
coverImageView.br_setImage(url: model?.image_url)
|
||||
titleLabel.text = model?.name
|
||||
categoryLabel.text = model?.category?.first
|
||||
|
||||
favoriteButton.isSelected = self.model?.is_collect ?? false
|
||||
|
||||
hotView.setNeedsUpdateConfiguration()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private lazy var coverImageView: BRImageView = {
|
||||
let imageView = BRImageView()
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontRegular(ofSize: 12)
|
||||
label.textColor = .color1C1C1C()
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var playIconImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "play_icon_02"))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var categoryLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontRegular(ofSize: 10)
|
||||
label.textColor = .color899D00()
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var hotView: UIButton = {
|
||||
var config = UIButton.Configuration.plain()
|
||||
config.image = UIImage(named: "hot_icon_02")
|
||||
config.imagePlacement = .leading
|
||||
config.imagePadding = 2
|
||||
config.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
|
||||
|
||||
let button = UIButton(configuration: config)
|
||||
button.isUserInteractionEnabled = false
|
||||
button.configurationUpdateHandler = { [weak self] button in
|
||||
guard let self = self else { return }
|
||||
let count = model?.watch_total ?? 0
|
||||
var string = "\(count)"
|
||||
if count > 100 {
|
||||
string = String(format: "%.1fk", Float(count) / 1000)
|
||||
}
|
||||
button.configuration?.attributedTitle = AttributedString.br_createAttributedString(string: string, color: .colorFF7489(), font: .fontRegular(ofSize: 10))
|
||||
|
||||
}
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var favoriteButton: UIButton = {
|
||||
let button = UIButton(type: .custom)
|
||||
button.setImage(UIImage(named: "favorite_icon_01"), for: .normal)
|
||||
button.setImage(UIImage(named: "favorite_icon_01_selected"), for: .selected)
|
||||
button.setImage(UIImage(named: "favorite_icon_01_selected"), for: [.selected, .highlighted])
|
||||
button.addTarget(self, action: #selector(handleFavoriteButton), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateShortFavoriteStateNotification), name: BRVideoAPI.updateShortFavoriteStateNotification, object: nil)
|
||||
|
||||
contentView.backgroundColor = .colorFFFFFF()
|
||||
contentView.layer.cornerRadius = 10
|
||||
contentView.layer.masksToBounds = true
|
||||
|
||||
br_setupUI()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
@objc private func handleFavoriteButton() {
|
||||
guard let shortPlayId = self.model?.short_play_id else { return }
|
||||
|
||||
let isFavorite = !(self.model?.is_collect ?? false)
|
||||
let videoId = self.model?.short_play_video_id
|
||||
|
||||
BRVideoAPI.requestFavorite(isFavorite: isFavorite, shortPlayId: shortPlayId, videoId: videoId) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func updateShortFavoriteStateNotification(sender: Notification) {
|
||||
guard let userInfo = sender.userInfo else { return }
|
||||
guard let shortPlayId = userInfo["id"] as? String else { return }
|
||||
guard let state = userInfo["state"] as? Bool else { return }
|
||||
guard shortPlayId == self.model?.short_play_id else { return }
|
||||
|
||||
self.model?.is_collect = state;
|
||||
|
||||
favoriteButton.isSelected = self.model?.is_collect ?? false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRSpotlightHotCell {
|
||||
|
||||
private func br_setupUI() {
|
||||
contentView.addSubview(coverImageView)
|
||||
contentView.addSubview(favoriteButton)
|
||||
contentView.addSubview(titleLabel)
|
||||
contentView.addSubview(playIconImageView)
|
||||
contentView.addSubview(categoryLabel)
|
||||
contentView.addSubview(hotView)
|
||||
|
||||
coverImageView.snp.makeConstraints { make in
|
||||
make.left.right.top.equalToSuperview()
|
||||
make.height.equalTo(160)
|
||||
}
|
||||
|
||||
favoriteButton.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview().offset(4)
|
||||
make.right.equalToSuperview().offset(-4)
|
||||
}
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(7)
|
||||
make.right.lessThanOrEqualToSuperview().offset(-7)
|
||||
make.top.equalTo(coverImageView.snp.bottom).offset(5)
|
||||
}
|
||||
|
||||
playIconImageView.snp.makeConstraints { make in
|
||||
make.right.equalToSuperview().offset(-4)
|
||||
make.bottom.equalToSuperview().offset(-5)
|
||||
}
|
||||
|
||||
categoryLabel.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(7)
|
||||
make.top.equalTo(coverImageView.snp.bottom).offset(26)
|
||||
}
|
||||
|
||||
hotView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(7)
|
||||
make.bottom.equalToSuperview().offset(-8)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
105
BeeReel/Class/Home/View/Spotlight/BRSpotlightHotMainCell.swift
Normal file
@ -0,0 +1,105 @@
|
||||
//
|
||||
// BRSpotlightHotMainCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRSpotlightHotMainCell: BRSpotlightMainBaseCell {
|
||||
|
||||
|
||||
override var moduleItem: BRHomeModuleItem? {
|
||||
didSet {
|
||||
|
||||
UIView.performWithoutAnimation { [weak self] in
|
||||
self?.collectionView.reloadData()
|
||||
}
|
||||
|
||||
self.collectionView.performBatchUpdates(nil) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
let height = moduleItem?.br_cellHeight ?? 0
|
||||
let contentHeight = self.collectionView.contentSize.height
|
||||
if height != contentHeight {
|
||||
self.collectionView.snp.updateConstraints { make in
|
||||
make.height.equalTo(contentHeight + 1)
|
||||
}
|
||||
moduleItem?.br_cellHeight = contentHeight
|
||||
|
||||
self.br_tableView?.reloadData()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||
let width = floor((UIScreen.width - 30 - 10) / 2)
|
||||
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.itemSize = .init(width: width, height: 222)
|
||||
layout.minimumInteritemSpacing = 10
|
||||
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.isScrollEnabled = false
|
||||
collectionView.register(BRSpotlightHotCell.self, forCellWithReuseIdentifier: "cell")
|
||||
return collectionView
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
br_setupUI()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRSpotlightHotMainCell {
|
||||
private func br_setupUI() {
|
||||
containerView.addSubview(collectionView)
|
||||
|
||||
collectionView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
make.height.equalTo(1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
|
||||
extension BRSpotlightHotMainCell: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! BRSpotlightHotCell
|
||||
cell.model = self.moduleItem?.list?[indexPath.row]
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return self.moduleItem?.list?.count ?? 0
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
let model = self.moduleItem?.list?[indexPath.row]
|
||||
|
||||
let vc = BRVideoDetailViewController()
|
||||
vc.shortPlayId = model?.short_play_id
|
||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
//
|
||||
// BRSpotlightMainBaseCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/27.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRSpotlightMainBaseCell: BRTableViewCell {
|
||||
|
||||
var moduleItem: BRHomeModuleItem? {
|
||||
didSet {
|
||||
titleStackView.br_removeAllArrangedSubview()
|
||||
|
||||
if let icon = moduleItem?.iconImage {
|
||||
iconImageView.image = icon
|
||||
titleStackView.addArrangedSubview(iconImageView)
|
||||
}
|
||||
titleLabel.text = moduleItem?.title
|
||||
titleStackView.addArrangedSubview(titleLabel)
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontMedium(ofSize: 15)
|
||||
label.textColor = .color1C1C1C()
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var iconImageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var titleStackView: UIStackView = {
|
||||
let view = UIStackView()
|
||||
view.axis = .horizontal
|
||||
view.spacing = 4
|
||||
return view
|
||||
}()
|
||||
|
||||
private(set) lazy var containerView: UIView = {
|
||||
let view = UIView()
|
||||
return view
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
contentView.addSubview(titleStackView)
|
||||
contentView.addSubview(containerView)
|
||||
|
||||
titleStackView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(15)
|
||||
make.top.equalToSuperview()
|
||||
make.height.equalTo(20)
|
||||
}
|
||||
|
||||
containerView.snp.makeConstraints { make in
|
||||
make.left.right.equalToSuperview()
|
||||
make.top.equalTo(titleStackView.snp.bottom).offset(10)
|
||||
make.bottom.equalToSuperview().offset(-30)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
107
BeeReel/Class/Home/View/Spotlight/BRSpotlightNewCell.swift
Normal file
@ -0,0 +1,107 @@
|
||||
//
|
||||
// BRSpotlightNewCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRSpotlightNewCell: BRCollectionViewCell {
|
||||
|
||||
var model: BRShortModel? {
|
||||
didSet {
|
||||
coverImageView.br_setImage(url: model?.image_url)
|
||||
titleLabel.text = model?.name
|
||||
categoryLabel.text = model?.category?.first
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var coverImageView: BRImageView = {
|
||||
let imageView = BRImageView()
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var bottomView: UIView = {
|
||||
let view = UIView()
|
||||
view.br_addEffectView(style: .light)
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontBold(ofSize: 12)
|
||||
label.textColor = .colorFFFFFF()
|
||||
label.layer.shadowColor = UIColor.color000000(alpha: 0.25).cgColor
|
||||
label.layer.shadowOpacity = 1
|
||||
label.layer.shadowRadius = 1
|
||||
label.layer.shadowOffset = .init(width: 0, height: 1)
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var categoryLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontMedium(ofSize: 10)
|
||||
label.textColor = .colorF1FF94()
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var playImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "play_icon_04"))
|
||||
imageView.setContentHuggingPriority(.required, for: .horizontal)
|
||||
imageView.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
return imageView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
br_setupUI()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRSpotlightNewCell {
|
||||
|
||||
private func br_setupUI() {
|
||||
contentView.layer.cornerRadius = 10
|
||||
contentView.layer.masksToBounds = true
|
||||
|
||||
contentView.addSubview(coverImageView)
|
||||
contentView.addSubview(bottomView)
|
||||
bottomView.addSubview(titleLabel)
|
||||
bottomView.addSubview(playImageView)
|
||||
bottomView.addSubview(categoryLabel)
|
||||
|
||||
coverImageView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
bottomView.snp.makeConstraints { make in
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
make.height.equalTo(52)
|
||||
}
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(8)
|
||||
make.top.equalToSuperview().offset(5)
|
||||
make.right.lessThanOrEqualToSuperview().offset(-8)
|
||||
}
|
||||
|
||||
playImageView.snp.makeConstraints { make in
|
||||
make.right.equalToSuperview().offset(-4)
|
||||
make.bottom.equalToSuperview().offset(-5)
|
||||
}
|
||||
|
||||
categoryLabel.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(playImageView)
|
||||
make.left.equalToSuperview().offset(8)
|
||||
make.right.lessThanOrEqualTo(playImageView.snp.left).offset(-5)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
//
|
||||
// BRSpotlightNewMainCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRSpotlightNewMainCell: BRSpotlightMainBaseCell {
|
||||
|
||||
override var moduleItem: BRHomeModuleItem? {
|
||||
didSet {
|
||||
|
||||
UIView.performWithoutAnimation { [weak self] in
|
||||
self?.collectionView.reloadData()
|
||||
}
|
||||
|
||||
self.collectionView.performBatchUpdates(nil) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
let height = moduleItem?.br_cellHeight ?? 0
|
||||
let contentHeight = self.collectionView.contentSize.height
|
||||
if height != contentHeight {
|
||||
self.collectionView.snp.updateConstraints { make in
|
||||
make.height.equalTo(contentHeight + 1)
|
||||
}
|
||||
moduleItem?.br_cellHeight = contentHeight
|
||||
|
||||
self.br_tableView?.reloadData()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||
let width = floor((UIScreen.width - 30 - 10) / 2)
|
||||
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.itemSize = .init(width: width, height: 222)
|
||||
layout.minimumLineSpacing = 10
|
||||
layout.minimumInteritemSpacing = 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.register(BRSpotlightNewCell.self, forCellWithReuseIdentifier: "cell")
|
||||
return collectionView
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
containerView.addSubview(collectionView)
|
||||
|
||||
collectionView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
make.height.equalTo(1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRSpotlightNewMainCell: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! BRSpotlightNewCell
|
||||
cell.model = self.moduleItem?.list?[indexPath.row]
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return moduleItem?.list?.count ?? 0
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
let model = self.moduleItem?.list?[indexPath.row]
|
||||
|
||||
let vc = BRVideoDetailViewController()
|
||||
vc.shortPlayId = model?.short_play_id
|
||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
//
|
||||
// BRSpotlightRecommandCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import FSPagerView
|
||||
|
||||
class BRSpotlightRecommandCell: FSPagerViewCell {
|
||||
|
||||
var model: BRShortModel? {
|
||||
didSet {
|
||||
coverImageView.br_setImage(url: model?.image_url)
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var coverImageView: BRImageView = {
|
||||
let imageView = BRImageView()
|
||||
return imageView
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.contentView.layer.shadowOpacity = 0
|
||||
|
||||
br_setupUI()
|
||||
|
||||
applyDiagonalMask()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
}
|
||||
|
||||
|
||||
private func applyDiagonalMask() {
|
||||
let maskLayer = CAShapeLayer()
|
||||
let path = UIBezierPath()
|
||||
let width = 150.0
|
||||
let height = 193.0
|
||||
let cut: CGFloat = 16 // 控制斜切角度
|
||||
let radius: CGFloat = 10 // 圆角半径
|
||||
|
||||
|
||||
path.move(to: CGPoint(x: 0, y: radius))
|
||||
path.addQuadCurve(to: CGPoint(x: radius, y: 0), controlPoint: CGPoint(x: 0, y: 0))
|
||||
|
||||
// 右上角
|
||||
path.addLine(to: CGPoint(x: width - radius, y: 0))
|
||||
path.addQuadCurve(to: CGPoint(x: width, y: radius), controlPoint: CGPoint(x: width, y: 0))
|
||||
// 右下角
|
||||
path.addLine(to: CGPoint(x: width - cut, y: height - radius))
|
||||
path.addQuadCurve(to: CGPoint(x: width - cut - radius, y: height), controlPoint: CGPoint(x: width - cut, y: height))
|
||||
// 左下角
|
||||
path.addLine(to: CGPoint(x: cut + radius, y: height))
|
||||
path.addQuadCurve(to: CGPoint(x: cut, y: height - radius), controlPoint: CGPoint(x: cut, y: height))
|
||||
path.close()
|
||||
|
||||
maskLayer.path = path.cgPath
|
||||
coverImageView.layer.mask = maskLayer
|
||||
}
|
||||
}
|
||||
|
||||
extension BRSpotlightRecommandCell {
|
||||
|
||||
private func br_setupUI() {
|
||||
contentView.addSubview(coverImageView)
|
||||
|
||||
coverImageView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
//
|
||||
// BRSpotlightRecommandMainCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import FSPagerView
|
||||
|
||||
class BRSpotlightRecommandMainCell: BRSpotlightMainBaseCell {
|
||||
|
||||
override var moduleItem: BRHomeModuleItem? {
|
||||
didSet {
|
||||
bannerView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private lazy var bannerView: FSPagerView = {
|
||||
let transformer = BRPagerViewTransformer(type: .ferrisWheel)
|
||||
transformer.minimumAlpha = 1
|
||||
|
||||
let view = FSPagerView()
|
||||
view.transformer = transformer
|
||||
view.decelerationDistance = FSPagerView.automaticDistance
|
||||
view.interitemSpacing = 0
|
||||
view.itemSize = .init(width: 150, height: 193)
|
||||
view.isInfinite = true
|
||||
view.delegate = self
|
||||
view.dataSource = self
|
||||
view.register(BRSpotlightRecommandCell.self, forCellWithReuseIdentifier: "cell")
|
||||
|
||||
return view
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
containerView.addSubview(bannerView)
|
||||
|
||||
bannerView.snp.makeConstraints { make in
|
||||
make.left.right.equalToSuperview()
|
||||
make.bottom.equalToSuperview()
|
||||
make.top.equalToSuperview().offset(-20)
|
||||
make.height.equalTo(193 + 40)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- FSPagerViewDataSource FSPagerViewDelegate --------------
|
||||
extension BRSpotlightRecommandMainCell: FSPagerViewDataSource, FSPagerViewDelegate {
|
||||
|
||||
func numberOfItems(in pagerView: FSPagerView) -> Int {
|
||||
return moduleItem?.list?.count ?? 0
|
||||
}
|
||||
|
||||
func pagerView(_ pagerView: FSPagerView, cellForItemAt index: Int) -> FSPagerViewCell {
|
||||
let cell = pagerView.dequeueReusableCell(withReuseIdentifier: "cell", at: index) as! BRSpotlightRecommandCell
|
||||
cell.model = moduleItem?.list?[index]
|
||||
return cell
|
||||
}
|
||||
|
||||
func pagerView(_ pagerView: FSPagerView, didSelectItemAt index: Int) {
|
||||
let model = self.moduleItem?.list?[index]
|
||||
|
||||
let vc = BRVideoDetailViewController()
|
||||
vc.shortPlayId = model?.short_play_id
|
||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
|
||||
|
||||
}
|
144
BeeReel/Class/Home/View/Spotlight/BRSpotlightTopCell.swift
Normal file
@ -0,0 +1,144 @@
|
||||
//
|
||||
// BRSpotlightTopCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRSpotlightTopCell: BRCollectionViewCell {
|
||||
|
||||
var model: BRShortModel? {
|
||||
didSet {
|
||||
coverImageView.br_setImage(url: model?.image_url)
|
||||
|
||||
titleLabel.text = model?.name
|
||||
categoryLabel.text = model?.category?.first
|
||||
|
||||
hotView.setNeedsUpdateConfiguration()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private lazy var coverImageView: BRImageView = {
|
||||
let imageView = BRImageView()
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var coverMarkView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "cover_mark_icon_01"))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var playImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "play_icon_03"))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var textBgView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "text_bg_image_01"))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontMedium(ofSize: 12)
|
||||
label.textColor = .color1C1C1C()
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var categoryLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontRegular(ofSize: 10)
|
||||
label.textColor = .color899D00()
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var hotView: UIButton = {
|
||||
var config = UIButton.Configuration.plain()
|
||||
config.image = UIImage(named: "hot_icon_02")
|
||||
config.imagePlacement = .leading
|
||||
config.imagePadding = 2
|
||||
config.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
|
||||
|
||||
let button = UIButton(configuration: config)
|
||||
button.isUserInteractionEnabled = false
|
||||
button.configurationUpdateHandler = { [weak self] button in
|
||||
guard let self = self else { return }
|
||||
let count = model?.watch_total ?? 0
|
||||
var string = "\(count)"
|
||||
if count > 100 {
|
||||
string = String(format: "%.1fk", Float(count) / 1000)
|
||||
}
|
||||
button.configuration?.attributedTitle = AttributedString.br_createAttributedString(string: string, 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 BRSpotlightTopCell {
|
||||
|
||||
private func br_setupUI() {
|
||||
contentView.layer.cornerRadius = 11
|
||||
contentView.layer.masksToBounds = true
|
||||
|
||||
|
||||
contentView.addSubview(coverImageView)
|
||||
coverImageView.addSubview(coverMarkView)
|
||||
contentView.addSubview(playImageView)
|
||||
coverImageView.addSubview(textBgView)
|
||||
textBgView.addSubview(titleLabel)
|
||||
textBgView.addSubview(categoryLabel)
|
||||
textBgView.addSubview(hotView)
|
||||
|
||||
coverImageView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
coverMarkView.snp.makeConstraints { make in
|
||||
make.right.bottom.equalToSuperview()
|
||||
}
|
||||
|
||||
playImageView.snp.makeConstraints { make in
|
||||
make.right.equalToSuperview().offset(0)
|
||||
make.bottom.equalToSuperview().offset(0)
|
||||
}
|
||||
|
||||
textBgView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(10)
|
||||
make.right.equalToSuperview().offset(-10)
|
||||
make.bottom.equalToSuperview().offset(-10)
|
||||
}
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(8)
|
||||
make.top.equalToSuperview().offset(7)
|
||||
make.right.lessThanOrEqualToSuperview().offset(-8)
|
||||
}
|
||||
|
||||
categoryLabel.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(8)
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(5)
|
||||
}
|
||||
|
||||
hotView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(8)
|
||||
make.bottom.equalToSuperview().offset(-10)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
//
|
||||
// BRSpotlightTopMainCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRSpotlightTopMainCell: BRSpotlightMainBaseCell {
|
||||
|
||||
|
||||
override var moduleItem: BRHomeModuleItem? {
|
||||
didSet {
|
||||
collectionView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.scrollDirection = .horizontal
|
||||
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
|
||||
layout.itemSize = .init(width: 210, height: 260)
|
||||
layout.minimumLineSpacing = 10
|
||||
return layout
|
||||
}()
|
||||
|
||||
private lazy var collectionView: BRCollectionView = {
|
||||
let collectionView = BRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||
collectionView.delegate = self
|
||||
collectionView.dataSource = self
|
||||
collectionView.showsHorizontalScrollIndicator = false
|
||||
collectionView.register(BRSpotlightTopCell.self, forCellWithReuseIdentifier: "cell")
|
||||
return collectionView
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||
|
||||
br_setupUI()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
extension BRSpotlightTopMainCell {
|
||||
|
||||
private func br_setupUI() {
|
||||
containerView.addSubview(collectionView)
|
||||
|
||||
collectionView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
make.height.equalTo(260)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
|
||||
extension BRSpotlightTopMainCell: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! BRSpotlightTopCell
|
||||
cell.model = self.moduleItem?.list?[indexPath.row]
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return self.moduleItem?.list?.count ?? 0
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
let model = self.moduleItem?.list?[indexPath.row]
|
||||
|
||||
let vc = BRVideoDetailViewController()
|
||||
vc.shortPlayId = model?.short_play_id
|
||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
}
|
67
BeeReel/Class/Home/ViewModel/BRHomeViewModel.swift
Normal file
@ -0,0 +1,67 @@
|
||||
//
|
||||
// BRHomeViewModel.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/26.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRHomeViewModel {
|
||||
|
||||
var homeOldDataArr: [BRHomeModuleItem] = [] {
|
||||
didSet {
|
||||
spotlightDataArr.removeAll()
|
||||
|
||||
//v3数据
|
||||
var item1: BRHomeModuleItem?
|
||||
//排行
|
||||
var item2: BRHomeModuleItem?
|
||||
//新品
|
||||
var item3: BRHomeModuleItem?
|
||||
|
||||
///其余分类数据
|
||||
var item4List: [BRHomeModuleItem] = []
|
||||
|
||||
homeOldDataArr.forEach {
|
||||
if $0.module_key == .banner {
|
||||
bannerArr = $0.list
|
||||
} else if $0.module_key == .v3_recommand {
|
||||
$0.iconImage = UIImage(named: "hot_icon_01")
|
||||
item1 = $0
|
||||
} else if $0.module_key == .week_ranking {
|
||||
$0.iconImage = UIImage(named: "top_icon_01")
|
||||
$0.title = "Top Charts".localized
|
||||
item2 = $0
|
||||
} else if $0.module_key == .new_recommand {
|
||||
$0.title = "Fresh Stories".localized
|
||||
item3 = $0
|
||||
} else if $0.module_key == .cagetory_recommand {
|
||||
item4List.append($0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if let item = item1 {
|
||||
spotlightDataArr.append(item)
|
||||
}
|
||||
|
||||
if let item = item2 {
|
||||
spotlightDataArr.append(item)
|
||||
}
|
||||
|
||||
if let item = item3 {
|
||||
spotlightDataArr.append(item)
|
||||
}
|
||||
|
||||
spotlightDataArr += item4List
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var bannerArr: [BRShortModel]?
|
||||
|
||||
var spotlightDataArr: [BRHomeModuleItem] = []
|
||||
|
||||
|
||||
}
|
156
BeeReel/Class/Player/Controller/BRPlayerListViewController.swift
Normal file
@ -0,0 +1,156 @@
|
||||
//
|
||||
// BRPlayerListViewController.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
@objc protocol BRPlayerListViewControllerDelegate {
|
||||
|
||||
///加载新数据
|
||||
@objc optional func br_playerViewControllerLoadNewDataV2(playerViewController: BRPlayerListViewController)
|
||||
|
||||
///将要加载更多数据
|
||||
@objc optional func br_playerViewControllerShouldLoadMoreData(playerViewController: BRPlayerListViewController) -> Bool
|
||||
///加载更多数据
|
||||
@objc optional func br_playerViewControllerLoadMoreData(playerViewController: BRPlayerListViewController)
|
||||
///向上加载更多数据
|
||||
@objc optional func br_playerViewControllerLoadUpMoreData(playerViewController: BRPlayerListViewController)
|
||||
|
||||
///当前展示的发生变化
|
||||
@objc optional func br_playerListViewController(_ viewController: BRPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath)
|
||||
|
||||
}
|
||||
|
||||
@objc protocol BRPlayerListViewControllerDataSource {
|
||||
|
||||
|
||||
func br_playerListViewController(_ viewController: BRPlayerListViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell
|
||||
|
||||
func br_playerListViewController(_ viewController: BRPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
|
||||
|
||||
}
|
||||
|
||||
class BRPlayerListViewController: BRViewController {
|
||||
|
||||
var contentSize: CGSize {
|
||||
return CGSize(width: UIScreen.width, height: UIScreen.height - UIScreen.customTabBarHeight)
|
||||
}
|
||||
|
||||
var CellClass: BRPlayerListCell.Type {
|
||||
return BRPlayerListCell.self
|
||||
}
|
||||
|
||||
private(set) lazy var viewModel = BRPlayerViewModel()
|
||||
|
||||
weak var delegate: BRPlayerListViewControllerDelegate?
|
||||
weak var dataSource: BRPlayerListViewControllerDataSource?
|
||||
|
||||
|
||||
private lazy var collectionViewLayout: UICollectionViewLayout = {
|
||||
let layout = UICollectionViewFlowLayout()
|
||||
layout.itemSize = contentSize
|
||||
layout.minimumInteritemSpacing = 0
|
||||
layout.minimumLineSpacing = 0
|
||||
return layout
|
||||
}()
|
||||
|
||||
private(set) lazy var collectionView: BRCollectionView = {
|
||||
let collectionView = BRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||
collectionView.delegate = self
|
||||
collectionView.dataSource = self
|
||||
collectionView.isPagingEnabled = true
|
||||
collectionView.showsVerticalScrollIndicator = false
|
||||
collectionView.showsHorizontalScrollIndicator = false
|
||||
collectionView.bounces = false
|
||||
collectionView.scrollsToTop = false
|
||||
collectionView.register(CellClass.self, forCellWithReuseIdentifier: "cell")
|
||||
return collectionView
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
br_setupUI()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
func play() {
|
||||
if self.isDidAppear {
|
||||
self.viewModel.currentPlayer?.start()
|
||||
}
|
||||
|
||||
self.viewModel.isPlaying = true
|
||||
|
||||
// if getDataCount() - viewModel.currentIndexPath.row <= 2 {
|
||||
// self.loadMoreData()
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRPlayerListViewController {
|
||||
private func br_setupUI() {
|
||||
view.addSubview(collectionView)
|
||||
|
||||
collectionView.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview()
|
||||
make.left.equalToSuperview()
|
||||
make.width.equalTo(self.contentSize.width)
|
||||
make.height.equalTo(self.contentSize.height)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: -------------- UICollectionViewDelegate UICollectionViewDataSource --------------
|
||||
extension BRPlayerListViewController: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
|
||||
if let newCell = self.dataSource?.br_playerListViewController(self, collectionView, cellForItemAt: indexPath, oldCell: cell) {
|
||||
cell = newCell
|
||||
}
|
||||
if let cell = cell as? BRPlayerListCell {
|
||||
if cell.viewModel == nil {
|
||||
cell.viewModel = viewModel
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
if let count = self.dataSource?.br_playerListViewController(self, collectionView, numberOfItemsInSection: section) {
|
||||
return count
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension BRPlayerListViewController {
|
||||
|
||||
private func loadMoreData() {
|
||||
let isLoad = self.delegate?.br_playerViewControllerShouldLoadMoreData?(playerViewController: self)
|
||||
if isLoad != false {
|
||||
self.delegate?.br_playerViewControllerLoadMoreData?(playerViewController: self)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadUpMoreData() {
|
||||
self.delegate?.br_playerViewControllerLoadUpMoreData?(playerViewController: self)
|
||||
}
|
||||
|
||||
private func didChangeIndexPathForVisible() {
|
||||
self.delegate?.br_playerListViewController?(self, didChangeIndexPathForVisible: viewModel.currentIndexPath)
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
//
|
||||
// BRVideoDetailViewController.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRVideoDetailViewController: BRPlayerListViewController {
|
||||
|
||||
|
||||
var shortPlayId: String?
|
||||
var activityId: String?
|
||||
|
||||
private var detailModel: BRVideoDetailModel?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
|
||||
self.requestDetailData()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension BRVideoDetailViewController {
|
||||
|
||||
private func requestDetailData(indexPath: IndexPath? = nil) {
|
||||
guard let shortPlayId = shortPlayId else { return }
|
||||
|
||||
|
||||
|
||||
|
||||
BRHUD.show(containerView: self.view)
|
||||
BRVideoAPI.requestVideoDetail(shortPlayId: shortPlayId, activityId: activityId) { [weak self] model in
|
||||
BRHUD.dismiss()
|
||||
|
||||
guard let self = self else { return }
|
||||
guard let model = model else { return }
|
||||
self.detailModel = model
|
||||
// self.videoNameLabel.text = model.shortPlayInfo?.name
|
||||
|
||||
/*
|
||||
self.reloadData { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let indexPath = indexPath {
|
||||
self.scrollToItem(indexPath: indexPath, animated: false)
|
||||
|
||||
} else if let videoInfo = self.detailModel?.video_info {
|
||||
var row: Int?
|
||||
self.detailModel?.episodeList?.enumerated().forEach({
|
||||
if $1.id == videoInfo.id {
|
||||
row = $0
|
||||
}
|
||||
})
|
||||
if let row = row {
|
||||
self.scrollToItem(indexPath: .init(row: row, section: 0), animated: false)
|
||||
} else {
|
||||
self.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false)
|
||||
}
|
||||
} else {
|
||||
self.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
}
|
42
BeeReel/Class/Player/Model/BRPlayerProtocol.swift
Normal file
@ -0,0 +1,42 @@
|
||||
//
|
||||
// BRPlayerProtocol.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc protocol BRPlayerProtocol: NSObjectProtocol {
|
||||
|
||||
|
||||
var videoInfo: BRVideoInfoModel? { get set }
|
||||
|
||||
var isCurrent: Bool { get set }
|
||||
|
||||
///上一集是否加锁
|
||||
@objc optional var hasLastEpisodeUnlocked: Bool { get set }
|
||||
|
||||
///总进度
|
||||
var duration: Int { get }
|
||||
///当前进度
|
||||
var currentPosition: Int { get }
|
||||
|
||||
var rate: Float { get set }
|
||||
|
||||
///播放准备
|
||||
func prepare()
|
||||
|
||||
///开始播放
|
||||
func start()
|
||||
|
||||
///暂停播放
|
||||
func pause()
|
||||
|
||||
///从头播放
|
||||
func replay()
|
||||
|
||||
///设置进度
|
||||
func seekToTime(toTime: Int)
|
||||
|
||||
}
|
24
BeeReel/Class/Player/Model/BRVideoDetailModel.swift
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// BRVideoDetailModel.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SmartCodable
|
||||
|
||||
class BRVideoDetailModel: BRModel, SmartCodable {
|
||||
|
||||
var business_model: String?
|
||||
var video_info: BRVideoInfoModel?
|
||||
var shortPlayInfo: BRShortModel?
|
||||
var episodeList: [BRVideoInfoModel]?
|
||||
var is_collect: Bool?
|
||||
var show_share_coin: Int?
|
||||
var share_coin: Int?
|
||||
var install_coins: Int?
|
||||
var revolution: Int?
|
||||
var unlock_video_ad_count: Int?
|
||||
var discount: Int?
|
||||
}
|
87
BeeReel/Class/Player/View/BRPlayerListCell.swift
Normal file
@ -0,0 +1,87 @@
|
||||
//
|
||||
// BRPlayerListCell.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRPlayerListCell: BRCollectionViewCell, BRPlayerProtocol {
|
||||
|
||||
|
||||
var viewModel: BRPlayerViewModel? {
|
||||
didSet {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var videoInfo: BRVideoInfoModel? {
|
||||
didSet {
|
||||
player.setPlayUrl(url: videoInfo?.video_url ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
var isCurrent: Bool = false
|
||||
|
||||
var duration: Int = 0
|
||||
|
||||
var currentPosition: Int = 0
|
||||
|
||||
var rate: Float = 1
|
||||
|
||||
func prepare() {
|
||||
|
||||
}
|
||||
|
||||
func start() {
|
||||
|
||||
}
|
||||
|
||||
func pause() {
|
||||
|
||||
}
|
||||
|
||||
func replay() {
|
||||
|
||||
}
|
||||
|
||||
func seekToTime(toTime: Int) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private lazy var player: BRPlayer = {
|
||||
let player = BRPlayer()
|
||||
player.playerView = self.playerView
|
||||
return player
|
||||
}()
|
||||
|
||||
private lazy var playerView: UIView = {
|
||||
let view = UIView()
|
||||
return view
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
br_setupUI()
|
||||
}
|
||||
|
||||
@MainActor required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRPlayerListCell {
|
||||
|
||||
private func br_setupUI() {
|
||||
contentView.addSubview(playerView)
|
||||
|
||||
playerView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
26
BeeReel/Class/Player/ViewModel/BRPlayerViewModel.swift
Normal file
@ -0,0 +1,26 @@
|
||||
//
|
||||
// BRPlayerViewModel.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRPlayerViewModel: NSObject {
|
||||
|
||||
|
||||
@objc dynamic var isPlaying: Bool = true
|
||||
|
||||
var currentIndexPath = IndexPath(row: 0, section: 0)
|
||||
|
||||
var currentPlayer: BRPlayerProtocol? {
|
||||
didSet {
|
||||
oldValue?.isCurrent = false
|
||||
oldValue?.pause()
|
||||
|
||||
self.currentPlayer?.isCurrent = true
|
||||
// self.currentPlayer?.rate = rateModel.rate.getRate()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
//
|
||||
// BRVideoRevolutionManager.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/30.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
class BRVideoRevolutionManager: NSObject {
|
||||
|
||||
static let manager = BRVideoRevolutionManager()
|
||||
|
||||
///当前分辨率
|
||||
lazy var revolution: BRShortModel.VideoRevolution = {
|
||||
let userInfo = BRLoginManager.manager.userInfo
|
||||
|
||||
if let revolution = UserDefaults.standard.object(forKey: kBRVideoRevolutionDefaultsKey) as? String {
|
||||
var revolution = verify(revolution: BRShortModel.VideoRevolution.init(rawValue: revolution) ?? .r_540)
|
||||
return revolution
|
||||
}
|
||||
return .r_540
|
||||
}()
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userInfoUpdateNotification), name: BRLoginManager.userInfoUpdateNotification, object: nil)
|
||||
}
|
||||
|
||||
@objc private func userInfoUpdateNotification() {
|
||||
self.setVideoRevolution(revolution: self.revolution)
|
||||
}
|
||||
|
||||
func setVideoRevolution(revolution: BRShortModel.VideoRevolution) {
|
||||
let newRevolution = verify(revolution: revolution)
|
||||
|
||||
if newRevolution != self.revolution {
|
||||
self.revolution = newRevolution
|
||||
NotificationCenter.default.post(name: BRVideoRevolutionManager.didChangeRevolutionNotification, object: nil)
|
||||
UserDefaults.standard.set(newRevolution.rawValue, forKey: kBRVideoRevolutionDefaultsKey)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRVideoRevolutionManager {
|
||||
|
||||
///校验分辨率是否可用,并返回可用分辨率
|
||||
func verify(revolution: BRShortModel.VideoRevolution) -> BRShortModel.VideoRevolution {
|
||||
let userInfo = BRLoginManager.manager.userInfo
|
||||
var newRevolution = revolution
|
||||
if userInfo?.is_vip != true {
|
||||
if newRevolution == .r_1080 {
|
||||
newRevolution = .r_720
|
||||
}
|
||||
if userInfo?.is_tourist != false, revolution != .r_540 {
|
||||
newRevolution = .r_540
|
||||
}
|
||||
}
|
||||
return newRevolution
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
extension BRVideoRevolutionManager {
|
||||
|
||||
///分辨率发生变化
|
||||
@objc static let didChangeRevolutionNotification = NSNotification.Name(rawValue: "VPVideoRevolutionManager.didChangeRevolutionNotification")
|
||||
}
|
||||
|
14
BeeReel/Delegate/AppDelegate+BRConfig.swift
Normal file
@ -0,0 +1,14 @@
|
||||
//
|
||||
// AppDelegate+BRConfig.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/25.
|
||||
//
|
||||
|
||||
extension AppDelegate {
|
||||
|
||||
func addConfig() {
|
||||
UIView.vp_Awake()
|
||||
}
|
||||
|
||||
}
|
41
BeeReel/Delegate/AppDelegate.swift
Normal file
@ -0,0 +1,41 @@
|
||||
//
|
||||
// AppDelegate.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
BRAppTool.appDelegate = self
|
||||
|
||||
BRLoginManager.manager.updateUserInfo(completer: nil)
|
||||
|
||||
addConfig()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: UISceneSession Lifecycle
|
||||
|
||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
// Called when a new scene session is being created.
|
||||
// Use this method to select a configuration to create the new scene with.
|
||||
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
|
||||
// Called when the user discards a scene session.
|
||||
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
|
||||
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
56
BeeReel/Delegate/SceneDelegate.swift
Normal file
@ -0,0 +1,56 @@
|
||||
//
|
||||
// SceneDelegate.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
guard let windowScene = (scene as? UIWindowScene) else { return }
|
||||
BRAppTool.sceneDelegate = self
|
||||
BRAppTool.windowScene = windowScene
|
||||
|
||||
window = UIWindow(windowScene: windowScene)
|
||||
|
||||
window?.rootViewController = BRTabBarController()
|
||||
window?.makeKeyAndVisible()
|
||||
}
|
||||
|
||||
func sceneDidDisconnect(_ scene: UIScene) {
|
||||
// Called as the scene is being released by the system.
|
||||
// This occurs shortly after the scene enters the background, or when its session is discarded.
|
||||
// Release any resources associated with this scene that can be re-created the next time the scene connects.
|
||||
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
|
||||
}
|
||||
|
||||
func sceneDidBecomeActive(_ scene: UIScene) {
|
||||
// Called when the scene has moved from an inactive state to an active state.
|
||||
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
|
||||
}
|
||||
|
||||
func sceneWillResignActive(_ scene: UIScene) {
|
||||
// Called when the scene will move from an active state to an inactive state.
|
||||
// This may occur due to temporary interruptions (ex. an incoming phone call).
|
||||
}
|
||||
|
||||
func sceneWillEnterForeground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the background to the foreground.
|
||||
// Use this method to undo the changes made on entering the background.
|
||||
}
|
||||
|
||||
func sceneDidEnterBackground(_ scene: UIScene) {
|
||||
// Called as the scene transitions from the foreground to the background.
|
||||
// Use this method to save data, release shared resources, and store enough scene-specific state information
|
||||
// to restore the scene back to its current state.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
55
BeeReel/Lib/AppTool/BRAppTool.swift
Normal file
@ -0,0 +1,55 @@
|
||||
//
|
||||
// BRAppTool.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class BRAppTool {
|
||||
|
||||
static var appDelegate: AppDelegate?
|
||||
static var sceneDelegate: SceneDelegate?
|
||||
|
||||
static var windowScene: UIWindowScene?
|
||||
|
||||
|
||||
static var keyWindow: UIWindow? {
|
||||
return windowScene?.keyWindow
|
||||
}
|
||||
|
||||
static var rootViewController: UIViewController? {
|
||||
return keyWindow?.rootViewController
|
||||
}
|
||||
|
||||
///获得启动图
|
||||
static var lanuchViewController: UIViewController? {
|
||||
let storyboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
|
||||
let vc = storyboard.instantiateInitialViewController()
|
||||
return vc
|
||||
}
|
||||
|
||||
static var topViewController: UIViewController? {
|
||||
var resultVC: UIViewController? = self.rootViewController
|
||||
if let rootNav = resultVC as? UINavigationController {
|
||||
resultVC = rootNav.topViewController
|
||||
}
|
||||
|
||||
resultVC = self._topViewController(resultVC)
|
||||
while resultVC?.presentedViewController != nil {
|
||||
resultVC = self._topViewController(resultVC?.presentedViewController)
|
||||
}
|
||||
return resultVC
|
||||
}
|
||||
|
||||
private static func _topViewController(_ vc: UIViewController?) -> UIViewController? {
|
||||
if vc is UINavigationController {
|
||||
return _topViewController((vc as? UINavigationController)?.topViewController)
|
||||
} else if vc is UITabBarController {
|
||||
return _topViewController((vc as? UITabBarController)?.selectedViewController)
|
||||
} else {
|
||||
return vc
|
||||
}
|
||||
}
|
||||
}
|
21
BeeReel/Lib/HUD/BRHUD.swift
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// BRHUD.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import SVProgressHUD
|
||||
|
||||
struct BRHUD {
|
||||
|
||||
static func show(containerView: UIView? = nil, type: SVProgressHUDMaskType = .clear) {
|
||||
SVProgressHUD.setContainerView(containerView)
|
||||
SVProgressHUD.setDefaultMaskType(type)
|
||||
SVProgressHUD.show()
|
||||
}
|
||||
|
||||
static func dismiss() {
|
||||
SVProgressHUD.dismiss()
|
||||
}
|
||||
}
|
22
BeeReel/Lib/HUD/BRToast.swift
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// BRToast.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import Toast
|
||||
|
||||
struct BRToast {
|
||||
|
||||
static func config() {
|
||||
CSToastManager.setTapToDismissEnabled(false)
|
||||
CSToastManager.setDefaultDuration(2)
|
||||
CSToastManager.setDefaultPosition(CSToastPositionCenter)
|
||||
}
|
||||
|
||||
static func show(text: String?) {
|
||||
guard let text = text else { return }
|
||||
BRAppTool.keyWindow?.makeToast(text)
|
||||
}
|
||||
}
|
122
BeeReel/Lib/LocalizedManager/BRLocalizedManager.swift
Normal file
@ -0,0 +1,122 @@
|
||||
//
|
||||
// BRLocalizedManager.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class BRLocalizedManager {
|
||||
|
||||
static let manager = BRLocalizedManager()
|
||||
|
||||
private let LocalizedUserDefaultsKey = "BRLocalizedManager.LocalizedUserDefaultsKey"
|
||||
private let LocalizedDataUserDefaultsKey = "BRLocalizedManager.LocalizedDataUserDefaultsKey"
|
||||
private let LocalizedDataLocalizedKeyUserDefaultsKey = "BRLocalizedManager.LocalizedDataLocalizedKeyUserDefaultsKey"
|
||||
|
||||
|
||||
///多语言数据
|
||||
private(set) lazy var localizedData: [String : String]? = UserDefaults.standard.object(forKey: LocalizedDataUserDefaultsKey) as? [String : String]
|
||||
{
|
||||
didSet {
|
||||
UserDefaults.standard.set(localizedData, forKey: LocalizedDataUserDefaultsKey)
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
}
|
||||
///当前语言数据对应的key
|
||||
private(set) lazy var localizedDataLocalizedKey: String? = UserDefaults.standard.object(forKey: LocalizedDataLocalizedKeyUserDefaultsKey) as? String
|
||||
{
|
||||
didSet {
|
||||
UserDefaults.standard.set(localizedDataLocalizedKey, forKey: LocalizedDataLocalizedKeyUserDefaultsKey)
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前语言代码(如果用户未手动设置,则返回系统语言)
|
||||
var currentLocalizedKey: String {
|
||||
get {
|
||||
return "en"
|
||||
// var key = UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) ?? Locale.preferredLanguages.first
|
||||
//
|
||||
// if key?.contains("zh-Hans") == true {
|
||||
// key = "zh"
|
||||
// } else if key?.contains("zh-Hant") == true {
|
||||
// key = "zh_hk"
|
||||
// } else {
|
||||
// let arr = key?.components(separatedBy: "-")
|
||||
// key = arr?.first
|
||||
// }
|
||||
// return key ?? "en"
|
||||
}
|
||||
set {
|
||||
UserDefaults.standard.set(newValue, forKey: LocalizedUserDefaultsKey)
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
}
|
||||
|
||||
var mjLocalizedKey: String {
|
||||
let key = currentLocalizedKey
|
||||
if key == "zh" {
|
||||
return "zh-Hans"
|
||||
} else if key == "zh_hk" {
|
||||
return "zh-Hant"
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// 判断是否跟随系统
|
||||
var isFollowingSystem: Bool {
|
||||
return UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey) == nil
|
||||
}
|
||||
|
||||
// 还原为系统默认语言
|
||||
func resetToSystemLanguage() {
|
||||
UserDefaults.standard.removeObject(forKey: LocalizedUserDefaultsKey)
|
||||
UserDefaults.standard.synchronize()
|
||||
}
|
||||
|
||||
// 获取本地化字符串
|
||||
func localizedString(forKey key: String, tableName: String? = nil) -> String {
|
||||
if let localizedData = localizedData,
|
||||
let text = localizedData[key] {
|
||||
return text
|
||||
|
||||
} else if let selectedLanguage = UserDefaults.standard.string(forKey: LocalizedUserDefaultsKey),
|
||||
let bundlePath = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"),
|
||||
let bundle = Bundle(path: bundlePath) {
|
||||
|
||||
return bundle.localizedString(forKey: key, value: nil, table: tableName)
|
||||
} else {
|
||||
return NSLocalizedString(key, tableName: tableName, bundle: .main, value: "", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension BRLocalizedManager {
|
||||
|
||||
static let localizedDidChangeNotification = Notification.Name(rawValue: "BRLocalizedManager.localizedDidChangeNotification")
|
||||
|
||||
}
|
||||
|
||||
extension String {
|
||||
var localized: String {
|
||||
var text = BRLocalizedManager.manager.localizedString(forKey: self)
|
||||
text = text.replacingOccurrences(of: "<br>", with: "\n")
|
||||
return text
|
||||
}
|
||||
|
||||
func localizedReplace(text: String) -> String {
|
||||
return self.localized.replacingOccurrences(of: "##", with: text)
|
||||
}
|
||||
|
||||
func localizedReplace(text1: String, text2: String, text3: String? = nil) -> String {
|
||||
var string = self.localized.replacingOccurrences(of: "#1#", with: text1)
|
||||
string = string.replacingOccurrences(of: "#2#", with: text2)
|
||||
if let text = text3 {
|
||||
string = string.replacingOccurrences(of: "#3#", with: text)
|
||||
}
|
||||
return string
|
||||
}
|
||||
}
|
127
BeeReel/Lib/Login/BRLoginManager.swift
Normal file
@ -0,0 +1,127 @@
|
||||
//
|
||||
// BRLoginManager.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SmartCodable
|
||||
|
||||
class BRLoginManager {
|
||||
|
||||
enum LoginType: String, SmartCaseDefaultable {
|
||||
case apple = "Apple"
|
||||
case faceBook = "Facebook"
|
||||
case google = "Google"
|
||||
case tiktok = "Tiktok"
|
||||
}
|
||||
|
||||
|
||||
static let manager = BRLoginManager()
|
||||
|
||||
private(set) var token = UserDefaults.br_object(forKey: kBRLoginTokenDefaultsKey, as: BRLoginToken.self)
|
||||
private(set) var userInfo = UserDefaults.br_object(forKey: kBRLoginUserInfoDefaultsKey, as: BRUserInfo.self)
|
||||
|
||||
///是否正在刷新token
|
||||
private(set) var isRefreshingToken = false
|
||||
|
||||
|
||||
|
||||
func setLoginToken(token: BRLoginToken?) {
|
||||
self.token = token
|
||||
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)?) {
|
||||
|
||||
BRUserAPI.requestUserInfo { [weak self] userInfo in
|
||||
guard let self = self else { return }
|
||||
if let userInfo = userInfo {
|
||||
self.userInfo = userInfo
|
||||
UserDefaults.br_setObject(userInfo, forKey: kBRLoginUserInfoDefaultsKey)
|
||||
NotificationCenter.default.post(name: BRLoginManager.userInfoUpdateNotification, object: nil)
|
||||
}
|
||||
completer?()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension BRLoginManager {
|
||||
|
||||
///登录状态发生变化
|
||||
@objc static let loginStateDidChangeNotification = NSNotification.Name(rawValue: "BRLoginManager.loginStateDidChangeNotification")
|
||||
///用户信息更新
|
||||
@objc static let userInfoUpdateNotification = NSNotification.Name(rawValue: "BRLoginManager.userInfoUpdateNotification")
|
||||
|
||||
}
|
43
BeeReel/Lib/Login/BRLoginToken.swift
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// BRLoginToken.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SmartCodable
|
||||
|
||||
class BRLoginToken: BRModel, SmartCodable, NSSecureCoding {
|
||||
|
||||
var token: String?
|
||||
var customer_id: String?
|
||||
var auto_login: Int?
|
||||
|
||||
required init() { }
|
||||
|
||||
static var supportsSecureCoding: Bool {
|
||||
get {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func encode(with coder: NSCoder) {
|
||||
coder.encode(token, forKey: "token")
|
||||
coder.encode(customer_id, forKey: "customer_id")
|
||||
coder.encode(auto_login, forKey: "auto_login")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init()
|
||||
|
||||
token = coder.decodeObject(of: NSString.self, forKey: "token") as? String
|
||||
customer_id = coder.decodeObject(of: NSString.self, forKey: "customer_id") as? String
|
||||
auto_login = coder.decodeObject(of: NSNumber.self, forKey: "auto_login")?.intValue
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
90
BeeReel/Lib/Player/BRPlayer.swift
Normal file
@ -0,0 +1,90 @@
|
||||
//
|
||||
// BRPlayer.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SJBaseVideoPlayer
|
||||
|
||||
|
||||
class BRPlayer {
|
||||
|
||||
private lazy var player: SJBaseVideoPlayer = {
|
||||
let player = SJBaseVideoPlayer()
|
||||
player.autoplayWhenSetNewAsset = false
|
||||
return player
|
||||
}()
|
||||
|
||||
var playerView: UIView? {
|
||||
didSet {
|
||||
playerView?.addSubview(player.view)
|
||||
player.view.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var duration: TimeInterval {
|
||||
return self.player.duration
|
||||
}
|
||||
|
||||
var currentTime: TimeInterval {
|
||||
return self.player.currentTime
|
||||
}
|
||||
|
||||
init() {
|
||||
setupPlayer()
|
||||
}
|
||||
|
||||
func setPlayUrl(url: String) {
|
||||
self.stop()
|
||||
guard let url = URL(string: url) else { return }
|
||||
let asset = SJVideoPlayerURLAsset(url: url)
|
||||
self.player.urlAsset = asset
|
||||
}
|
||||
|
||||
func start() {
|
||||
self.player.play()
|
||||
}
|
||||
|
||||
func pause() {
|
||||
self.player.pause()
|
||||
}
|
||||
|
||||
func stop() {
|
||||
self.player.stop()
|
||||
}
|
||||
|
||||
func seek(toTime: Int) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension BRPlayer {
|
||||
|
||||
private func setupPlayer() {
|
||||
//播放完成回调
|
||||
self.player.playbackObserver.playbackDidFinishExeBlock = { [weak self] player in
|
||||
guard let self = self else { return }
|
||||
|
||||
}
|
||||
//播放状态改变
|
||||
self.player.playbackObserver.playbackStatusDidChangeExeBlock = { [weak self] player in
|
||||
guard let self = self else { return }
|
||||
|
||||
}
|
||||
//播放时长改变
|
||||
self.player.playbackObserver.durationDidChangeExeBlock = { [weak self] player in
|
||||
guard let self = self else { return }
|
||||
|
||||
}
|
||||
//播放进度改变
|
||||
self.player.playbackObserver.currentTimeDidChangeExeBlock = { [weak self] player in
|
||||
guard let self = self else { return }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
72
BeeReel/Lib/User/BRUserInfo.swift
Normal file
@ -0,0 +1,72 @@
|
||||
//
|
||||
// BRUserInfo.swift
|
||||
// BeeReel
|
||||
//
|
||||
// Created by 湖南秦九 on 2025/6/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SmartCodable
|
||||
|
||||
class BRUserInfo: BRModel, SmartCodable, NSSecureCoding {
|
||||
|
||||
var id: String?
|
||||
var customer_id: String?
|
||||
var is_guide_vip: String?
|
||||
///是否为游客登录
|
||||
var is_tourist: Bool?
|
||||
var family_name: String?
|
||||
var giving_name: String?
|
||||
var vip_end_time: TimeInterval?
|
||||
var third_access_id: String?
|
||||
var is_vip: Bool?
|
||||
var coin_left_total: Int?
|
||||
var send_coin_left_total: Int?
|
||||
var vip_type: String?
|
||||
var email: String?
|
||||
var third_access_platform: String?
|
||||
var ip_address: String?
|
||||
var country_code: String?
|
||||
var user_level: String?
|
||||
var avator: String?
|
||||
var sign_in_status: String?
|
||||
var registered_days: String?
|
||||
var ln: String?
|
||||
var country: String?
|
||||
|
||||
|
||||
var totalCoin: Int {
|
||||
return (coin_left_total ?? 0) + (send_coin_left_total ?? 0)
|
||||
}
|
||||
|
||||
required init() { }
|
||||
|
||||
static var supportsSecureCoding: Bool {
|
||||
get {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func encode(with coder: NSCoder) {
|
||||
coder.encode(id, forKey: "id")
|
||||
coder.encode(customer_id, forKey: "customer_id")
|
||||
coder.encode(is_tourist, forKey: "is_tourist")
|
||||
coder.encode(is_vip, forKey: "is_vip")
|
||||
coder.encode(avator, forKey: "avator")
|
||||
coder.encode(family_name, forKey: "family_name")
|
||||
coder.encode(giving_name, forKey: "giving_name")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init()
|
||||
id = coder.decodeObject(of: NSString.self, forKey: "id") as? String
|
||||
customer_id = coder.decodeObject(of: NSString.self, forKey: "customer_id") as? String
|
||||
is_tourist = coder.decodeObject(of: NSNumber.self, forKey: "is_tourist")?.boolValue
|
||||
is_vip = coder.decodeObject(of: NSNumber.self, forKey: "is_vip")?.boolValue
|
||||
avator = coder.decodeObject(of: NSString.self, forKey: "avator") as? String
|
||||
family_name = coder.decodeObject(of: NSString.self, forKey: "family_name") as? String
|
||||
giving_name = coder.decodeObject(of: NSString.self, forKey: "giving_name") as? String
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "tinted"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
6
BeeReel/Sources/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
6
BeeReel/Sources/Assets.xcassets/icon/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
22
BeeReel/Sources/Assets.xcassets/icon/cover_mark_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Exclude@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Exclude@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
BeeReel/Sources/Assets.xcassets/icon/cover_mark_icon_01.imageset/Exclude@2x.png
vendored
Normal file
After Width: | Height: | Size: 937 B |
BIN
BeeReel/Sources/Assets.xcassets/icon/cover_mark_icon_01.imageset/Exclude@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
BeeReel/Sources/Assets.xcassets/icon/favorite_icon_01.imageset/Component 2@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
BeeReel/Sources/Assets.xcassets/icon/favorite_icon_01.imageset/Component 2@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.2 KiB |
22
BeeReel/Sources/Assets.xcassets/icon/favorite_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Component 2@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Component 2@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
BeeReel/Sources/Assets.xcassets/icon/favorite_icon_01_selected.imageset/Component 2@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
BeeReel/Sources/Assets.xcassets/icon/favorite_icon_01_selected.imageset/Component 2@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.2 KiB |
22
BeeReel/Sources/Assets.xcassets/icon/favorite_icon_01_selected.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Component 2@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Component 2@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
22
BeeReel/Sources/Assets.xcassets/icon/home_header_icon_01.imageset/Contents.json
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
BIN
BeeReel/Sources/Assets.xcassets/icon/home_header_icon_01.imageset/顶部卡片bg@2x.png
vendored
Normal file
After Width: | Height: | Size: 79 KiB |
BIN
BeeReel/Sources/Assets.xcassets/icon/home_header_icon_01.imageset/顶部卡片bg@3x.png
vendored
Normal file
After Width: | Height: | Size: 154 KiB |
22
BeeReel/Sources/Assets.xcassets/icon/hot_icon_01.imageset/Contents.json
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
BIN
BeeReel/Sources/Assets.xcassets/icon/hot_icon_01.imageset/hot@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
BeeReel/Sources/Assets.xcassets/icon/hot_icon_01.imageset/hot@3x.png
vendored
Normal file
After Width: | Height: | Size: 4.8 KiB |
22
BeeReel/Sources/Assets.xcassets/icon/hot_icon_02.imageset/Contents.json
vendored
Normal file
@ -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
|
||||
}
|
||||
}
|
BIN
BeeReel/Sources/Assets.xcassets/icon/hot_icon_02.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 598 B |
BIN
BeeReel/Sources/Assets.xcassets/icon/hot_icon_02.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 810 B |
BIN
BeeReel/Sources/Assets.xcassets/icon/play_icon_01.imageset/Component 1@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
BeeReel/Sources/Assets.xcassets/icon/play_icon_01.imageset/Component 1@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.9 KiB |
22
BeeReel/Sources/Assets.xcassets/icon/play_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Component 1@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Component 1@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
BeeReel/Sources/Assets.xcassets/icon/play_icon_02.imageset/Component 1@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
BeeReel/Sources/Assets.xcassets/icon/play_icon_02.imageset/Component 1@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.5 KiB |