第一次上传,首页开发

This commit is contained in:
zjx 2025-06-30 18:18:27 +08:00
parent afc2670dc6
commit 69b2658e2c
173 changed files with 8973 additions and 1 deletions

4
.gitignore vendored
View File

@ -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

File diff suppressed because it is too large Load Diff

View 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
}
}

View 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()
}
}
}

View 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)?) {}
}

View 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!)
}
}

View File

@ -0,0 +1,17 @@
//
// BRUserDefaultsKey.swift
// BeeReel
//
// Created by on 2025/6/24.
//
///token
let kBRLoginTokenDefaultsKey = "kBRLoginTokenDefaultsKey"
///
let kBRLoginUserInfoDefaultsKey = "kBRLoginUserInfoDefaultsKey"
///
let kBRVideoRevolutionDefaultsKey = "kBRVideoRevolutionDefaultsKey"

View 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
]))
}
}

View 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();
}
}

View File

@ -0,0 +1,12 @@
//
// String+BRAdd.swift
// BeeReel
//
// Created by on 2025/6/24.
//
import SmartCodable
extension String: SmartCodable {
}

View 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)
}
}

View 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
}
}

View 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)
}
}

View 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
}
}
}
}

View 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
}
}

View 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()
}
}
}

View 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
}
}

View 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
}
}
}

View 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?
}

View File

@ -0,0 +1,15 @@
//
// BRModel.swift
// BeeReel
//
// Created by on 2025/6/24.
//
import UIKit
class BRModel: NSObject {
required override init() {
super.init()
}
}

View 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)
}
}
}

View 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)
}
}
}

View 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")
}

View 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
}
}
}

View 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
}

View 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)
}
}

View 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"

View 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")
}
}

View 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)
}
}

View 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)
}
}

View 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
}
}
}

View 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
}
}

View 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)
}
}

View 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")
}
}

View 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
}
}

View 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?()
}
}

View File

@ -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
}
}
}

View 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()
}
}
}

View File

@ -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
}
}

View 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)
}
}
}
}

View 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
}
}

View 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"]
]
}
}

View 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?
///12:
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?
}

View 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)
}
}
}

View 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)
}
}
}

View 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)
}
}
}

View 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)
}
}

View File

@ -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")
}
}

View 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)
}
}
}

View File

@ -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)
}
}

View File

@ -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()
}
}
}

View File

@ -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)
}
}

View 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)
}
}
}

View File

@ -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)
}
}

View 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] = []
}

View 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)
}
}

View File

@ -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)
}
}
*/
}
}
}

View 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)
}

View 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?
}

View 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()
}
}
}

View 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()
}
}
}

View File

@ -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")
}

View File

@ -0,0 +1,14 @@
//
// AppDelegate+BRConfig.swift
// BeeReel
//
// Created by on 2025/6/25.
//
extension AppDelegate {
func addConfig() {
UIView.vp_Awake()
}
}

View 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.
}
}

View 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.
}
}

View 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
}
}
}

View 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()
}
}

View 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)
}
}

View 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
}
}

View 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")
}

View 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
}
}

View 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 }
}
}
}

View 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
}
}

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View 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
}
}

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View 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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Some files were not shown because too many files have changed in this diff Show More