diff --git a/BeeReel.xcodeproj/project.pbxproj b/BeeReel.xcodeproj/project.pbxproj index 6b15043..d4fed8e 100644 --- a/BeeReel.xcodeproj/project.pbxproj +++ b/BeeReel.xcodeproj/project.pbxproj @@ -172,6 +172,10 @@ F39855482E33928400E2D28D /* BRStoreCoinBigCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39855472E33928400E2D28D /* BRStoreCoinBigCell.swift */; }; F398554A2E33929C00E2D28D /* BRStoreCoinCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39855492E33929C00E2D28D /* BRStoreCoinCell.swift */; }; F398554C2E3392C200E2D28D /* BRStoreCoinSmallCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F398554B2E3392C200E2D28D /* BRStoreCoinSmallCell.swift */; }; + F398554E2E34699F00E2D28D /* BRVideoLockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F398554D2E34699F00E2D28D /* BRVideoLockView.swift */; }; + F39855502E34782200E2D28D /* BRVideoUnlockModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F398554F2E34782200E2D28D /* BRVideoUnlockModel.swift */; }; + F39855522E347BDE00E2D28D /* BRVideoRechargeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39855512E347BDE00E2D28D /* BRVideoRechargeView.swift */; }; + F39855542E34A49500E2D28D /* VPPayDataRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39855532E34A49500E2D28D /* VPPayDataRequest.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -355,6 +359,10 @@ F39855472E33928400E2D28D /* BRStoreCoinBigCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRStoreCoinBigCell.swift; sourceTree = ""; }; F39855492E33929C00E2D28D /* BRStoreCoinCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRStoreCoinCell.swift; sourceTree = ""; }; F398554B2E3392C200E2D28D /* BRStoreCoinSmallCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRStoreCoinSmallCell.swift; sourceTree = ""; }; + F398554D2E34699F00E2D28D /* BRVideoLockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRVideoLockView.swift; sourceTree = ""; }; + F398554F2E34782200E2D28D /* BRVideoUnlockModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRVideoUnlockModel.swift; sourceTree = ""; }; + F39855512E347BDE00E2D28D /* BRVideoRechargeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRVideoRechargeView.swift; sourceTree = ""; }; + F39855532E34A49500E2D28D /* VPPayDataRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPPayDataRequest.swift; sourceTree = ""; }; F70FA1F4169364C4C53534CE /* Pods-BeeReel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BeeReel.release.xcconfig"; path = "Target Support Files/Pods-BeeReel/Pods-BeeReel.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -816,6 +824,8 @@ BF02B7EA2E2E388800172177 /* BREpisodeMenuView.swift */, BF02B7F02E2E55E300172177 /* BRRateSelectorView.swift */, BF02B7F22E2E571600172177 /* BRRateSelectorCell.swift */, + F398554D2E34699F00E2D28D /* BRVideoLockView.swift */, + F39855512E347BDE00E2D28D /* BRVideoRechargeView.swift */, ); path = View; sourceTree = ""; @@ -827,6 +837,7 @@ BFC676802E122733006659E5 /* BRPlayerProtocol.swift */, BFC676862E122E36006659E5 /* BRVideoDetailModel.swift */, BF02B7EE2E2E4BFD00172177 /* BRRateModel.swift */, + F398554F2E34782200E2D28D /* BRVideoUnlockModel.swift */, ); path = Model; sourceTree = ""; @@ -1073,6 +1084,7 @@ isa = PBXGroup; children = ( F398553D2E336D3000E2D28D /* BRPayDateModel.swift */, + F39855532E34A49500E2D28D /* VPPayDataRequest.swift */, ); path = Model; sourceTree = ""; @@ -1207,12 +1219,14 @@ BFC676B12E137D2F006659E5 /* BRPopularPicksViewController.swift in Sources */, BF02B7FC2E2F262F00172177 /* BRGradientView.swift in Sources */, BFC676692E0E34DA006659E5 /* BRUserAPI.swift in Sources */, + F398554E2E34699F00E2D28D /* BRVideoLockView.swift in Sources */, BFC676782E0E9553006659E5 /* BRSpotlightMainBaseCell.swift in Sources */, BFC676732E0E938B006659E5 /* BRTableView.swift in Sources */, BFC676932E126A62006659E5 /* BRSpotlightNewMainCell.swift in Sources */, BFC6768D2E123D6E006659E5 /* AttributedString+BRAdd.swift in Sources */, BF02B8392E30B30400172177 /* AlignedCollectionViewFlowLayout.swift in Sources */, BF3A56882E30E0DD009E5CF9 /* BREmpty.swift in Sources */, + F39855542E34A49500E2D28D /* VPPayDataRequest.swift in Sources */, BF3338F52E1616B200B10F76 /* BRExploreControlView.swift in Sources */, F398552D2E33126D00E2D28D /* BRMineCoinItemView.swift in Sources */, BF692B132E0A7B9000A5C2DA /* BRUserInfo.swift in Sources */, @@ -1250,6 +1264,7 @@ BF692B782E0D3A1200A5C2DA /* BRHomeModuleItem.swift in Sources */, BF692B5A2E0AAADD00A5C2DA /* BRPlayerListCell.swift in Sources */, BF02B8312E30897700172177 /* BRSearchHomeView.swift in Sources */, + F39855502E34782200E2D28D /* BRVideoUnlockModel.swift in Sources */, F398554A2E33929C00E2D28D /* BRStoreCoinCell.swift in Sources */, BF3A568C2E30EBA2009E5CF9 /* BRHomePlayRecordButton.swift in Sources */, BF692B162E0A7CD600A5C2DA /* BRHUD.swift in Sources */, @@ -1287,6 +1302,7 @@ F39855312E33620200E2D28D /* BRMineStoreCell.swift in Sources */, BF692B182E0A7D8900A5C2DA /* BRToast.swift in Sources */, BF692B0E2E0A7AF300A5C2DA /* UserDefaults+BRAdd.swift in Sources */, + F39855522E347BDE00E2D28D /* BRVideoRechargeView.swift in Sources */, BF02B8082E2F616E00172177 /* BRFavoritesViewController.swift in Sources */, BF3338FD2E1626B000B10F76 /* BRPlayerControlProtocol.swift in Sources */, BF692B582E0AAA6F00A5C2DA /* UIScreen+BRAdd.swift in Sources */, diff --git a/BeeReel/Base/Extension/UIColor+BRAdd.swift b/BeeReel/Base/Extension/UIColor+BRAdd.swift index f740307..439b2cb 100644 --- a/BeeReel/Base/Extension/UIColor+BRAdd.swift +++ b/BeeReel/Base/Extension/UIColor+BRAdd.swift @@ -186,4 +186,8 @@ extension UIColor { static func colorFFB635(alpha: CGFloat = 1) -> UIColor { return UIColor(rgb: 0xFFB635, alpha: alpha) } + + static func colorB5B5B5(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0xB5B5B5, alpha: alpha) + } } diff --git a/BeeReel/Base/Network/API/BRVideoAPI.swift b/BeeReel/Base/Network/API/BRVideoAPI.swift index b00610f..00bcd6a 100644 --- a/BeeReel/Base/Network/API/BRVideoAPI.swift +++ b/BeeReel/Base/Network/API/BRVideoAPI.swift @@ -134,6 +134,38 @@ class BRVideoAPI { } } + ///上报播放时长 + static func requestUploadPlayTime(shortPlayId: String, videoId: String, seconds: Int) { + + var param = BRNetworkParameters(path: "/uploadHistorySeconds") + param.isLoding = false + param.isToast = false + param.parameters = [ + "video_id" : videoId, + "short_play_id" : shortPlayId, + "play_seconds" : seconds + ] + + BRNetwork.request(parameters: param) { (response: BRNetworkResponse) in + + } + } + + ///金币解锁视频 + static func requestCoinUnlockVideo(shortPlayId: String, videoId: String, completer: ((_ model: BRVideoUnlockModel?) -> Void)?) { + + var param = BRNetworkParameters(path: "/buy_video") + param.isLoding = true + param.parameters = [ + "short_play_id" : shortPlayId, + "video_id" : videoId, + ] + + BRNetwork.request(parameters: param) { (response: BRNetworkResponse) in + completer?(response.data) + } + } + } diff --git a/BeeReel/Base/View/BRPanModalContentView.swift b/BeeReel/Base/View/BRPanModalContentView.swift index 3b8aca6..3fdbc00 100644 --- a/BeeReel/Base/View/BRPanModalContentView.swift +++ b/BeeReel/Base/View/BRPanModalContentView.swift @@ -72,9 +72,9 @@ class BRPanModalContentView: HWPanModalContentView { return false } - override func minVerticalVelocityToTriggerDismiss() -> CGFloat { - return 0 - } +// override func minVerticalVelocityToTriggerDismiss() -> CGFloat { +// return 0 +// } override func showsScrollableVerticalScrollIndicator() -> Bool { return false diff --git a/BeeReel/Class/Player/Controller/BRPlayerListViewController.swift b/BeeReel/Class/Player/Controller/BRPlayerListViewController.swift index 4d0ac8d..dacbdf9 100644 --- a/BeeReel/Class/Player/Controller/BRPlayerListViewController.swift +++ b/BeeReel/Class/Player/Controller/BRPlayerListViewController.swift @@ -49,6 +49,7 @@ class BRPlayerListViewController: BRViewController { private(set) lazy var viewModel: BRPlayerViewModel = { let vm = BRPlayerViewModel() vm.delegate = self + vm.playerListVC = self return vm }() @@ -321,6 +322,14 @@ extension BRPlayerListViewController: BRPlayerViewModelDelegate { } + func br_playProgressDidChange(viewModel: BRPlayerViewModel, time: TimeInterval) { + + } + + func br_needUpdateAllData(viewModel: BRPlayerViewModel, scrollTo indexPath: IndexPath?) { + + } + } extension BRPlayerListViewController { diff --git a/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift b/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift index 93cde2a..c7ba1d4 100644 --- a/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift +++ b/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift @@ -23,6 +23,9 @@ class BRVideoDetailViewController: BRPlayerListViewController { private var detailArr: [BRVideoDetailModel] = [] + ///上一次上报播放时长的节点 + private var lastUploadTime: TimeInterval = 0 + //MARK: UI属性 private lazy var backButton: UIButton = { let button = UIButton(type: .custom) @@ -66,8 +69,21 @@ class BRVideoDetailViewController: BRPlayerListViewController { } override func play() { - super.play() - BRVideoAPI.requestAddPlayHistory(videoId: self.viewModel.currentPlayer?.videoInfo?.short_play_video_id, shortPlayId: self.viewModel.currentPlayer?.videoInfo?.short_play_id) + let videoInfo = self.viewModel.currentPlayer?.videoInfo + guard videoInfo?.is_lock == true else { + super.play() + BRVideoAPI.requestAddPlayHistory(videoId: self.viewModel.currentPlayer?.videoInfo?.short_play_video_id, shortPlayId: self.viewModel.currentPlayer?.videoInfo?.short_play_id) + return + } + + + self.pause() + + let myCoins = BRLoginManager.manager.userInfo?.totalCoin ?? 0 + let coins = videoInfo?.coins ?? 0 + if myCoins < coins, self.viewModel.currentPlayer?.hasLastEpisodeUnlocked != true { + self.viewModel.openRechargeView() + } } } @@ -116,6 +132,18 @@ extension BRVideoDetailViewController { self.popUpView = view } + + override func br_playProgressDidChange(viewModel: BRPlayerViewModel, time: TimeInterval) { + if (time >= lastUploadTime + 5 || time < lastUploadTime) && time >= 5 { + lastUploadTime = time + self.viewModel.uploadPlayTime() + } + } + + override func br_needUpdateAllData(viewModel: BRPlayerViewModel, scrollTo indexPath: IndexPath?) { + self.requestDetailData(indexPath: indexPath) + } + } //MARK: -------------- BRPlayerListViewControllerDataSource BRPlayerListViewControllerDelegate -------------- @@ -147,6 +175,8 @@ extension BRVideoDetailViewController: BRPlayerListViewControllerDataSource, BRP return false } } + + } extension BRVideoDetailViewController { @@ -169,6 +199,23 @@ extension BRVideoDetailViewController { self.reloadData { [weak self] in guard let self = self else { return } self.play() + var targetIndexPath = IndexPath(row: 0, section: 0) + + if let indexPath = indexPath, indexPath.row < (model.episodeList?.count ?? 0) { + targetIndexPath = indexPath + + } else if let videoInfo = model.video_info { + var row: Int? + model.episodeList?.enumerated().forEach({ + if $1.id == videoInfo.id { + row = $0 + } + }) + if let row = row { + targetIndexPath = .init(row: row, section: 0) + } + } + self.scrollToItem(indexPath: targetIndexPath, animated: false) } } diff --git a/BeeReel/Class/Player/Model/BRRateModel.swift b/BeeReel/Class/Player/Model/BRRateModel.swift index 52fe407..1485875 100644 --- a/BeeReel/Class/Player/Model/BRRateModel.swift +++ b/BeeReel/Class/Player/Model/BRRateModel.swift @@ -50,7 +50,7 @@ class BRRateModel: NSObject { static func getAllRate() -> [BRRateModel] { return [ - BRRateModel(rate: .x0_25), +// BRRateModel(rate: .x0_25), BRRateModel(rate: .x0_5), BRRateModel(rate: .x0_75), BRRateModel(rate: .x1), diff --git a/BeeReel/Class/Player/Model/BRVideoUnlockModel.swift b/BeeReel/Class/Player/Model/BRVideoUnlockModel.swift new file mode 100644 index 0000000..c425cd6 --- /dev/null +++ b/BeeReel/Class/Player/Model/BRVideoUnlockModel.swift @@ -0,0 +1,26 @@ +// +// BRVideoUnlockModel.swift +// BeeReel +// +// Created by 长沙鸿瑶 on 2025/7/26. +// + +import UIKit +import SmartCodable + +class BRVideoUnlockModel: BRModel, SmartCodable { + + enum ResponseStatus: String, SmartCaseDefaultable { + ///前面还有没购买的剧 + case jump = "jump" + ///没找到视频 + case noPlay = "no_play" + ///金币不足跳充值 + case notEnough = "not_enough" + ///购买成功 + case success = "success" + } + + var status: ResponseStatus? + +} diff --git a/BeeReel/Class/Player/View/BRDetailControlView.swift b/BeeReel/Class/Player/View/BRDetailControlView.swift index d8bb929..1c3e6ea 100644 --- a/BeeReel/Class/Player/View/BRDetailControlView.swift +++ b/BeeReel/Class/Player/View/BRDetailControlView.swift @@ -19,6 +19,12 @@ class BRDetailControlView: BRPlayerControlView { override var videoInfo: BRVideoInfoModel? { didSet { epButton.videoInfo = videoInfo + videoLockView.videoInfo = videoInfo + if videoInfo?.is_lock == true { + self.videoLockView.isHidden = false + } else { + self.videoLockView.isHidden = true + } } } @@ -108,6 +114,14 @@ class BRDetailControlView: BRPlayerControlView { return button }() + private lazy var videoLockView: BRVideoLockView = { + let view = BRVideoLockView() + view.clickUnlockButton = { [weak self] in + self?.viewModel?.clickUnlockButton() + } + return view + }() + deinit { NotificationCenter.default.removeObserver(self) @@ -133,7 +147,6 @@ class BRDetailControlView: BRPlayerControlView { } } - } extension BRDetailControlView { @@ -198,6 +211,7 @@ extension BRDetailControlView { addSubview(rateButton) addSubview(nameLabel) addSubview(favoriteButton) + addSubview(videoLockView) progressView.snp.makeConstraints { make in make.left.equalToSuperview() @@ -233,6 +247,10 @@ extension BRDetailControlView { make.right.equalToSuperview().offset(-7) make.bottom.equalTo(progressView.snp.top).offset(-10) } + + videoLockView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } } } diff --git a/BeeReel/Class/Player/View/BREpisodeSelectorCell.swift b/BeeReel/Class/Player/View/BREpisodeSelectorCell.swift index 49896ea..191f0ba 100644 --- a/BeeReel/Class/Player/View/BREpisodeSelectorCell.swift +++ b/BeeReel/Class/Player/View/BREpisodeSelectorCell.swift @@ -12,6 +12,8 @@ class BREpisodeSelectorCell: BRCollectionViewCell { var model: BRVideoInfoModel? { didSet { epLabel.text = model?.episode + + lockView.isHidden = !(model?.is_lock ?? false) } } @@ -39,6 +41,19 @@ class BREpisodeSelectorCell: BRCollectionViewCell { return imageView }() + private lazy var lockView: UIView = { + let view = UIView() + view.backgroundColor = .colorE3FC37() + view.br_setRoundedCorner(topLeft: 0, topRight: 6, bottomLeft: 6, bottomRight: 0) + let icon = UIImageView(image: UIImage(named: "Frame 3")) + view.addSubview(icon) + icon.snp.makeConstraints { make in + make.center.equalToSuperview() + } + + return view + }() + override init(frame: CGRect) { super.init(frame: frame) @@ -51,6 +66,7 @@ class BREpisodeSelectorCell: BRCollectionViewCell { contentView.addSubview(epLabel) contentView.addSubview(lightImageView) + contentView.addSubview(lockView) epLabel.snp.makeConstraints { make in make.center.equalToSuperview() @@ -60,6 +76,12 @@ class BREpisodeSelectorCell: BRCollectionViewCell { make.right.top.equalToSuperview() } + lockView.snp.makeConstraints { make in + make.top.right.equalToSuperview() + make.width.equalTo(18) + make.height.equalTo(12) + } + } @MainActor required init?(coder: NSCoder) { diff --git a/BeeReel/Class/Player/View/BRPlayerControlView.swift b/BeeReel/Class/Player/View/BRPlayerControlView.swift index fac91b3..ae18072 100644 --- a/BeeReel/Class/Player/View/BRPlayerControlView.swift +++ b/BeeReel/Class/Player/View/BRPlayerControlView.swift @@ -88,7 +88,17 @@ class BRPlayerControlView: UIView, BRPlayerControlProtocol { self.viewModel?.switchPlayAndPause() } - + func updatePlayIconState() { + if videoInfo?.is_lock == true { + self.playIconImageView.isHidden = true + } else { + if isCurrent == true, self.viewModel?.isPlaying != true { + self.playIconImageView.isHidden = false + } else { + self.playIconImageView.isHidden = true + } + } + } } @@ -107,13 +117,6 @@ extension BRPlayerControlView { extension BRPlayerControlView { - private func updatePlayIconState() { - if isCurrent == true, self.viewModel?.isPlaying != true { - self.playIconImageView.isHidden = false - } else { - self.playIconImageView.isHidden = true - } - - } + } diff --git a/BeeReel/Class/Player/View/BRPlayerListCell.swift b/BeeReel/Class/Player/View/BRPlayerListCell.swift index 771ce0f..46d4327 100644 --- a/BeeReel/Class/Player/View/BRPlayerListCell.swift +++ b/BeeReel/Class/Player/View/BRPlayerListCell.swift @@ -75,6 +75,9 @@ class BRPlayerListCell: BRCollectionViewCell, BRPlayerProtocol { self.player.seek(toTime: time) } + func seekTo(time: TimeInterval) { + self.player.seek(toTime: time) + } var ControlViewClass: BRPlayerControlView.Type { return BRPlayerControlView.self @@ -144,6 +147,7 @@ extension BRPlayerListCell: BRPlayerDelegate { } else { self.controlView.progress = time / player.duration } + self.viewModel?.playProgressDidChange(time: time) } func br_playerInBufferToPlay(_ player: BRPlayer) { @@ -153,4 +157,11 @@ extension BRPlayerListCell: BRPlayerDelegate { func br_playerBufferingCompleted(_ player: BRPlayer) { self.controlView.isLoading = false } + + func br_playerReadyToPlay(_ player: BRPlayer) { + let time = TimeInterval(self.videoInfo?.play_seconds ?? 0) / 1000 + if time > 1 { + self.seekTo(time: time) + } + } } diff --git a/BeeReel/Class/Player/View/BRVideoLockView.swift b/BeeReel/Class/Player/View/BRVideoLockView.swift new file mode 100644 index 0000000..646fc0d --- /dev/null +++ b/BeeReel/Class/Player/View/BRVideoLockView.swift @@ -0,0 +1,128 @@ +// +// BRVideoLockView.swift +// BeeReel +// +// Created by 长沙鸿瑶 on 2025/7/26. +// + +import UIKit + +class BRVideoLockView: UIView { + + + var videoInfo: BRVideoInfoModel? { + didSet { + lockButton.setNeedsUpdateConfiguration() + } + } + + var clickUnlockButton: (() -> Void)? + + private lazy var lockIconView: UIImageView = { + let view = UIImageView(image: UIImage(named: "Frame 4")) + return view + }() + + private lazy var bottomView: UIView = { + let view = UIImageView(image: UIImage(named: "bg")) + view.isUserInteractionEnabled = true + return view + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = .fontBold(ofSize: 18) + label.textColor = .colorFFFFFF() + label.text = "Unlock to Continue".localized + return label + }() + + private lazy var lockButton: UIButton = { + var config = UIButton.Configuration.plain() + config.background.image = UIImage(named: "bg 1") + config.image = UIImage(named: "Frame 5") + config.imagePadding = 10 + + let button = UIButton(configuration: config) + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + + let title = "Unlocking costs ## Coins".localizedReplace(text: "\(videoInfo?.coins ?? 0)") + + button.configuration?.attributedTitle = AttributedString.br_createAttributedString(string: title, color: .color1C1C1C(), font: .fontRegular(ofSize: 14)) + } + button.addTarget(self, action: #selector(handleUnlockButton), for: .touchUpInside) + return button + }() + + private lazy var totalCoinsLabel: UILabel = { + let label = UILabel() + label.font = .fontRegular(ofSize: 12) + label.textColor = .colorB5B5B5() + return label + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = .color000000(alpha: 0.4) + NotificationCenter.default.addObserver(self, selector: #selector(updateUserInfo), name: BRLoginManager.userInfoUpdateNotification, object: nil) + + updateUserInfo() + br_setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func updateUserInfo() { + let userInfo = BRLoginManager.manager.userInfo + totalCoinsLabel.text = "Balance: ## Coins".localizedReplace(text: "\(userInfo?.totalCoin ?? 0)") + } + + @objc private func handleUnlockButton() { + self.clickUnlockButton?() + } +} + +extension BRVideoLockView { + + private func br_setupUI() { + addSubview(lockIconView) + addSubview(bottomView) + bottomView.addSubview(titleLabel) + bottomView.addSubview(lockButton) + bottomView.addSubview(totalCoinsLabel) + + lockIconView.snp.makeConstraints { make in + make.center.equalToSuperview() + } + + bottomView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.height.equalTo(UIScreen.tabbarSafeBottomMargin + 222) + } + + titleLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalToSuperview().offset(26) + } + + lockButton.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.centerY.equalToSuperview() + make.width.equalTo(260) + make.height.equalTo(48) + } + + totalCoinsLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.bottom.equalToSuperview().offset(-(UIScreen.tabbarSafeBottomMargin + 10)) + } + } + +} diff --git a/BeeReel/Class/Player/View/BRVideoRechargeView.swift b/BeeReel/Class/Player/View/BRVideoRechargeView.swift new file mode 100644 index 0000000..e54da36 --- /dev/null +++ b/BeeReel/Class/Player/View/BRVideoRechargeView.swift @@ -0,0 +1,232 @@ +// +// BRVideoRechargeView.swift +// BeeReel +// +// Created by 长沙鸿瑶 on 2025/7/26. +// + +import UIKit + +class BRVideoRechargeView: BRPanModalContentView { + + var buyCoinsFinishBlock: (() -> Void)? + var buyVipFinishBlock: (() -> Void)? + + var payDataModel: BRPayDateModel? { + didSet { + self.stackView.br_removeAllArrangedSubview() + self.vipView.list = payDataModel?.list_sub_vip ?? [] + self.coinView.list = payDataModel?.list_coins ?? [] + + if let sort = payDataModel?.sort, sort.count > 0 { + sort.forEach { + if $0 == .vip, payDataModel?.list_sub_vip?.isEmpty == false { + self.stackView.addArrangedSubview(self.vipView) + } else if $0 == .coin, payDataModel?.list_coins?.isEmpty == false { + self.stackView.addArrangedSubview(self.coinView) + } + } + } else { + if payDataModel?.list_sub_vip?.isEmpty == false { + self.stackView.addArrangedSubview(self.vipView) + } + if payDataModel?.list_coins?.isEmpty == false { + self.stackView.addArrangedSubview(self.coinView) + } + } + + self.setNeedsLayoutUpdate() + } + } + + var unlockCoin: Int? { + didSet { + if let unlockCoin = self.unlockCoin, unlockCoin > 0 { + unlockCoinsView.isHidden = false + } else { + unlockCoinsView.isHidden = true + } + unlockCoinsView.setNeedsUpdateConfiguration() + } + } + + var shortPlayId: String? { + didSet { + vipView.shortPlayId = shortPlayId + coinView.shortPlayId = shortPlayId + } + } + var videoId: String? { + didSet { + vipView.videoId = videoId + coinView.videoId = videoId + } + } + + private lazy var coinIconView: UIView = { + let imageView = UIImageView(image: UIImage(named: "Frame 6")) + return imageView + }() + + private lazy var coinTitleLabel: UILabel = { + let label = UILabel() + label.font = .fontMedium(ofSize: 13) + label.textColor = .colorFFFFFF() + label.text = "My Coins:".localized + return label + }() + + private lazy var coinLabel: UILabel = { + let label = UILabel() + label.font = .fontMedium(ofSize: 13) + label.textColor = .colorE3FC37() + return label + }() + + private lazy var scrollView: BRScrollView = { + let scrollView = BRScrollView() + scrollView.bounces = false + return scrollView + }() + + private lazy var unlockCoinsView: UIButton = { + var config = UIButton.Configuration.plain() + config.background.backgroundColor = .colorE3FC37() + config.image = UIImage(named: "Frame 6") + config.imagePadding = 2 + config.imagePlacement = .trailing + config.contentInsets = .init(top: 0, leading: 10, bottom: 0, trailing: 10) + + let button = UIButton(configuration: config) + button.layer.cornerRadius = 13 + button.layer.masksToBounds = true + button.isUserInteractionEnabled = false + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + let title = "Unlock:".localized + let coins = " \(unlockCoin ?? 0)" + + var attributedTitle = AttributedString.br_createAttributedString(string: title + coins, color: .color1C1C1C(), font: .fontRegular(ofSize: 13)) + if let range = attributedTitle.range(of: coins) { + attributedTitle[range].font = UIFont.fontMedium(ofSize: 13) + attributedTitle[range].foregroundColor = UIColor.colorFF7489() + } + button.configuration?.attributedTitle = attributedTitle + } + + return button + }() + + private lazy var stackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = 12 + return stackView + }() + + private lazy var vipView: BRStoreVipView = { + let view = BRStoreVipView() + view.buyFinishBlock = { [weak self] in + self?.buyVipFinishBlock?() + self?.dismiss(animated: true) { + + } + } + return view + }() + + private lazy var coinView: BRStoreCoinView = { + let view = BRStoreCoinView() + view.buyFinishBlock = { [weak self] in + self?.buyCoinsFinishBlock?() + self?.dismiss(animated: true) { + + } + } + return view + }() + + deinit { + NotificationCenter.default.removeObserver(self) + } + + override init(frame: CGRect) { + super.init(frame: frame) + NotificationCenter.default.addObserver(self, selector: #selector(updateUserInfo), name: BRLoginManager.userInfoUpdateNotification, object: nil) + self.contentHeight = UIScreen.height - UIScreen.navBarHeight + self.mainScrollView = self.scrollView + + br_setupUI() + + updateUserInfo() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + + override func allowsPullDownWhenShortState() -> Bool { + return true + } + + override func allowsDragToDismiss() -> Bool { + return true + } + +} + +extension BRVideoRechargeView { + + private func br_setupUI() { + addSubview(scrollView) + scrollView.addSubview(stackView) + addSubview(coinTitleLabel) + addSubview(coinLabel) + addSubview(coinIconView) + addSubview(unlockCoinsView) + + scrollView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + make.top.equalToSuperview().offset(50) + } + + stackView.snp.makeConstraints { make in + make.top.left.equalToSuperview() + make.width.equalTo(UIScreen.width) + make.bottom.equalToSuperview().offset(-UIScreen.tabbarSafeBottomMargin - 10) + } + + coinTitleLabel.snp.makeConstraints { make in + make.left.equalToSuperview().offset(15) + make.centerY.equalTo(coinIconView) + } + + coinLabel.snp.makeConstraints { make in + make.left.equalTo(coinTitleLabel.snp.right) + make.centerY.equalTo(coinIconView) + } + + coinIconView.snp.makeConstraints { make in + make.left.equalTo(coinLabel.snp.right).offset(2) + make.top.equalToSuperview().offset(18) + } + + unlockCoinsView.snp.makeConstraints { make in + make.centerY.equalTo(coinIconView) + make.right.equalToSuperview().offset(-15) + make.height.equalTo(26) + } + + } +} + +extension BRVideoRechargeView { + + @objc private func updateUserInfo() { + let userInfo = BRLoginManager.manager.userInfo + + coinLabel.text = " \(userInfo?.totalCoin ?? 0)" + } + +} diff --git a/BeeReel/Class/Player/ViewModel/BRPlayerViewModel.swift b/BeeReel/Class/Player/ViewModel/BRPlayerViewModel.swift index 33504c6..11f1630 100644 --- a/BeeReel/Class/Player/ViewModel/BRPlayerViewModel.swift +++ b/BeeReel/Class/Player/ViewModel/BRPlayerViewModel.swift @@ -10,6 +10,8 @@ import UIKit @objc protocol BRPlayerViewModelDelegate { + @objc func br_playProgressDidChange(viewModel: BRPlayerViewModel, time: TimeInterval) + @objc func br_currentVideoPlayFinish(viewModel: BRPlayerViewModel) @objc func br_switchPlayAndPause(viewModel: BRPlayerViewModel) @@ -17,6 +19,9 @@ import UIKit @objc func br_onEpisodeView(viewModel: BRPlayerViewModel) @objc func br_clickRateButton(viewModel: BRPlayerViewModel) + + @objc func br_needUpdateAllData(viewModel: BRPlayerViewModel, scrollTo indexPath: IndexPath?) + } @@ -24,6 +29,8 @@ class BRPlayerViewModel: NSObject { weak var delegate: BRPlayerViewModelDelegate? + weak var playerListVC: BRPlayerListViewController? + @objc dynamic var isPlaying: Bool = true var currentIndexPath = IndexPath(row: 0, section: 0) @@ -44,12 +51,22 @@ class BRPlayerViewModel: NSObject { self.currentPlayer?.rate = rateModel.rate.getRate() } } + + private lazy var payDataRequest = VPPayDataRequest() } extension BRPlayerViewModel { + ///播放进度变更 + func playProgressDidChange(time: TimeInterval) { + if time > 1 { + self.currentPlayer?.videoInfo?.play_seconds = Int(time) * 1000 + } + self.delegate?.br_playProgressDidChange(viewModel: self, time: time) + } + func playFinish(player: BRPlayerProtocol) { guard (player as? UICollectionViewCell) == (currentPlayer as? UICollectionViewCell) else { return } self.delegate?.br_currentVideoPlayFinish(viewModel: self) @@ -73,7 +90,106 @@ extension BRPlayerViewModel { ///设置进度 func seekTo(progress: Float) { self.currentPlayer?.seekTo(progress: progress) + } + + ///点击解锁按钮 + func clickUnlockButton() { + unlockVideo { [weak self] finish in + if finish { + self?.playerListVC?.reloadData { + self?.playerListVC?.play() + } + } + } + } + + ///更新全部数据 + func updateAllData(scrollTo indexPath: IndexPath? = nil) { + self.delegate?.br_needUpdateAllData(viewModel: self, scrollTo: indexPath) } } + + +extension BRPlayerViewModel { + func unlockVideo(completer: ((_ finish: Bool) -> Void)?) { + let videoInfo = self.currentPlayer?.videoInfo + guard let shortPlayId = videoInfo?.short_play_id, let videoId = videoInfo?.short_play_video_id else { return } + + BRVideoAPI.requestCoinUnlockVideo(shortPlayId: shortPlayId, videoId: videoId) { [weak self] model in + guard let self = self else { return } + guard let model = model else { + completer?(false) + return + } + switch model.status { + case .jump: + BRToast.show(text: "beereel_jump_unlock_error".localized) + completer?(false) + + case .noPlay: + BRToast.show(text: "beereel_buy_fail_toast_01".localized) + completer?(false) + + case .notEnough: + self.openRechargeView() + completer?(false) + + case .success: + //更新用户信息 + BRLoginManager.manager.updateUserInfo { + videoInfo?.is_lock = false + completer?(true) + } + + default: + completer?(false) + break + } + } + } + + ///打开充值页面 + func openRechargeView() { + guard let videoInfo = self.currentPlayer?.videoInfo else { return } + + self.payDataRequest.requestProducts(isLoding: true) { [weak self] model in + guard let self = self else { return } + guard let model = model else { return } + let view = BRVideoRechargeView() + view.shortPlayId = videoInfo.short_play_id + view.videoId = videoInfo.short_play_video_id + view.payDataModel = model + view.unlockCoin = self.currentPlayer?.videoInfo?.coins + view.buyVipFinishBlock = { [weak self] in + guard let self = self else { return } + self.updateAllData(scrollTo: self.currentIndexPath) + } + view.buyCoinsFinishBlock = { [weak self] in + guard let self = self else { return } + self.updateAllData(scrollTo: self.currentIndexPath) + } + view.present(in: nil) + } + } + + + ///上报播放进度 + func uploadPlayTime() { + let videoInfo = self.currentPlayer?.videoInfo + let currentTime = self.currentPlayer?.currentTime ?? 0 + let duration = self.currentPlayer?.durationTime ?? 0 + + var time = currentTime + if currentTime >= duration { + time = 0 + } + + guard let shortPlayId = videoInfo?.short_play_id, let videoId = videoInfo?.short_play_video_id else { return } + + //上报播放时长 + BRVideoAPI.requestUploadPlayTime(shortPlayId: shortPlayId, videoId: videoId, seconds: Int(time) * 1000) + + } +} diff --git a/BeeReel/Class/Store/Controller/BRStoreViewController.swift b/BeeReel/Class/Store/Controller/BRStoreViewController.swift index fd7decf..99a9e11 100644 --- a/BeeReel/Class/Store/Controller/BRStoreViewController.swift +++ b/BeeReel/Class/Store/Controller/BRStoreViewController.swift @@ -11,6 +11,8 @@ class BRStoreViewController: BRViewController { private var payData: BRPayDateModel? + private lazy var dataRequest = VPPayDataRequest() + private lazy var scrollView: BRScrollView = { let scrollView = BRScrollView() return scrollView @@ -95,8 +97,7 @@ extension BRStoreViewController { extension BRStoreViewController { private func requestPayData() { - - BRStoreAPI.requestPayTemplate { [weak self] model in + self.dataRequest.requestProducts { [weak self] model in guard let self = self else { return } guard let model = model else { return } @@ -123,7 +124,6 @@ extension BRStoreViewController { } self.stackView.addArrangedSubview(self.tipView) - } } diff --git a/BeeReel/Class/Store/Model/VPPayDataRequest.swift b/BeeReel/Class/Store/Model/VPPayDataRequest.swift new file mode 100644 index 0000000..6dfa4ce --- /dev/null +++ b/BeeReel/Class/Store/Model/VPPayDataRequest.swift @@ -0,0 +1,114 @@ +// +// VPPayDataRequest.swift +// BeeReel +// +// Created by 长沙鸿瑶 on 2025/7/26. +// + +import UIKit +import StoreKit + +class VPPayDataRequest: NSObject { + + private var oldTemplateModel: BRPayDateModel? + + private var completerBlock: ((_ model: BRPayDateModel?) -> Void)? + + private var isLoding = false + private var isToast = false + + + func requestProducts(isLoding: Bool = false, isToast: Bool = true, completer: ((_ model: BRPayDateModel?) -> Void)?) { + self.completerBlock = completer + self.isLoding = isLoding + self.isToast = isToast + + if isLoding { + BRHUD.show() + } + + BRStoreAPI.requestPayTemplate { [weak self] model in + guard let self = self else { return } + if isLoding { + BRHUD.dismiss() + } + completer?(model) + + } + +// BRStoreAPI.requestPayTemplate(isToast: isToast) { [weak self] model in +// guard let self = self else { return } +// guard let model = model else { +// if isLoding { +// VPHUD.dismiss() +// } +// self.completerBlock?(nil) +// return +// } +// self.oldTemplateModel = model +// +// var productIdArr: [String] = [] +// model.list_sub_vip?.forEach { item in +// productIdArr.append(VPIAPManager.manager.getProductId(templateId: item.ios_template_id) ?? "") +// } +// model.list_coins?.forEach { item in +// productIdArr.append(VPIAPManager.manager.getProductId(templateId: item.ios_template_id) ?? "") +// } +// +// let set = Set(productIdArr) +// let productsRequest = SKProductsRequest(productIdentifiers: set) +// productsRequest.delegate = self +// productsRequest.start() +// +// } + } + +} + +/* + //MARK: -------------- SKProductsRequestDelegate -------------- + extension VPPayTemplateRequest: SKProductsRequestDelegate { + + func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { + if isLoding { + VPHUD.dismiss() + } + + guard let templateModel = self.oldTemplateModel else { return } + let products = response.products + + var newCoinList: [VPPayTemplateItem] = [] + var newVipList: [VPPayTemplateItem] = [] + + templateModel.list_coins?.forEach { item in + let productId = VPIAPManager.manager.getProductId(templateId: item.ios_template_id) ?? "" + for product in products { + if productId == product.productIdentifier { + item.price = product.price.stringValue + item.currency = product.priceLocale.currencySymbol + newCoinList.append(item) + break + } + } + } + templateModel.list_sub_vip?.forEach { item in + let productId = VPIAPManager.manager.getProductId(templateId: item.ios_template_id) ?? "" + for product in products { + if productId == product.productIdentifier { + item.price = product.price.stringValue + item.currency = product.priceLocale.currencySymbol + newVipList.append(item) + break + } + } + } + templateModel.list_coins = newCoinList + templateModel.list_sub_vip = newVipList + + DispatchQueue.main.async { + self.completerBlock?(templateModel) + } + } + } + + */ diff --git a/BeeReel/Class/Store/View/BRStoreCoinView.swift b/BeeReel/Class/Store/View/BRStoreCoinView.swift index 0748b87..b117794 100644 --- a/BeeReel/Class/Store/View/BRStoreCoinView.swift +++ b/BeeReel/Class/Store/View/BRStoreCoinView.swift @@ -9,6 +9,8 @@ import UIKit class BRStoreCoinView: UIView { + var buyFinishBlock: (() -> Void)? + var list: [BRPayItem] = [] { didSet { @@ -30,6 +32,9 @@ class BRStoreCoinView: UIView { } } + var shortPlayId: String? + var videoId: String? + private var newList: [[BRPayItem]] = [] private lazy var titleLabel: UILabel = { diff --git a/BeeReel/Class/Store/View/BRStoreVipView.swift b/BeeReel/Class/Store/View/BRStoreVipView.swift index 174199c..4220c8e 100644 --- a/BeeReel/Class/Store/View/BRStoreVipView.swift +++ b/BeeReel/Class/Store/View/BRStoreVipView.swift @@ -9,12 +9,17 @@ import UIKit class BRStoreVipView: UIView { + var buyFinishBlock: (() -> Void)? + var list: [BRPayItem] = [] { didSet { self.collectionView.reloadData() } } + var shortPlayId: String? + var videoId: String? + private lazy var titleLabel: UILabel = { let label = UILabel() label.textColor = .colorFFFFFF() diff --git a/BeeReel/Lib/Player/BRPlayer.swift b/BeeReel/Lib/Player/BRPlayer.swift index 112e898..247f57d 100644 --- a/BeeReel/Lib/Player/BRPlayer.swift +++ b/BeeReel/Lib/Player/BRPlayer.swift @@ -10,6 +10,8 @@ import SJBaseVideoPlayer @objc protocol BRPlayerDelegate: NSObjectProtocol { + + @objc optional func br_playerReadyToPlay(_ player: BRPlayer) ///更新当前总进度 @objc optional func br_playerDurationDidChange(_ player: BRPlayer, duration: TimeInterval) ///更新当前进度 @@ -150,8 +152,6 @@ extension BRPlayer { //播放控制改变的回调 self.player.playbackObserver.timeControlStatusDidChangeExeBlock = { [weak self] player in guard let self = self else { return } -// , player.reasonForWaitingToPlay == SJWaitingToMinimizeStallsReason - if player.timeControlStatus == .waitingToPlay {//缓冲中 self.delegate?.br_playerInBufferToPlay?(self) brLog(message: "=======缓冲中 === \(player.reasonForWaitingToPlay ?? "")") @@ -161,6 +161,14 @@ extension BRPlayer { } } + self.player.playbackObserver.assetStatusDidChangeExeBlock = { [weak self] player in + guard let self = self else { return } + brLog(message: "assetStatus === \(player.assetStatus.rawValue)") + if player.assetStatus == .readyToPlay { + self.delegate?.br_playerReadyToPlay?(self) + } + } + //播放时长改变 self.player.playbackObserver.durationDidChangeExeBlock = { [weak self] player in guard let self = self else { return } diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 3.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/Frame 3.imageset/Contents.json new file mode 100644 index 0000000..5c4d3b1 --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/Frame 3.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 3.imageset/Frame@2x.png b/BeeReel/Sources/Assets.xcassets/icon/Frame 3.imageset/Frame@2x.png new file mode 100644 index 0000000..a22f17e Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/Frame 3.imageset/Frame@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 3.imageset/Frame@3x.png b/BeeReel/Sources/Assets.xcassets/icon/Frame 3.imageset/Frame@3x.png new file mode 100644 index 0000000..1c10935 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/Frame 3.imageset/Frame@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 4.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/Frame 4.imageset/Contents.json new file mode 100644 index 0000000..5c4d3b1 --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/Frame 4.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 4.imageset/Frame@2x.png b/BeeReel/Sources/Assets.xcassets/icon/Frame 4.imageset/Frame@2x.png new file mode 100644 index 0000000..9fe1b9a Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/Frame 4.imageset/Frame@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 4.imageset/Frame@3x.png b/BeeReel/Sources/Assets.xcassets/icon/Frame 4.imageset/Frame@3x.png new file mode 100644 index 0000000..e289e76 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/Frame 4.imageset/Frame@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 5.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/Frame 5.imageset/Contents.json new file mode 100644 index 0000000..5c4d3b1 --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/Frame 5.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 5.imageset/Frame@2x.png b/BeeReel/Sources/Assets.xcassets/icon/Frame 5.imageset/Frame@2x.png new file mode 100644 index 0000000..848f3c4 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/Frame 5.imageset/Frame@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 5.imageset/Frame@3x.png b/BeeReel/Sources/Assets.xcassets/icon/Frame 5.imageset/Frame@3x.png new file mode 100644 index 0000000..cec9ee3 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/Frame 5.imageset/Frame@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 6.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/Frame 6.imageset/Contents.json new file mode 100644 index 0000000..5c4d3b1 --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/Frame 6.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 6.imageset/Frame@2x.png b/BeeReel/Sources/Assets.xcassets/icon/Frame 6.imageset/Frame@2x.png new file mode 100644 index 0000000..f9c97b7 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/Frame 6.imageset/Frame@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/Frame 6.imageset/Frame@3x.png b/BeeReel/Sources/Assets.xcassets/icon/Frame 6.imageset/Frame@3x.png new file mode 100644 index 0000000..b465d82 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/Frame 6.imageset/Frame@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/bg 1.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/bg 1.imageset/Contents.json new file mode 100644 index 0000000..36577cc --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/bg 1.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/bg 1.imageset/bg@2x.png b/BeeReel/Sources/Assets.xcassets/icon/bg 1.imageset/bg@2x.png new file mode 100644 index 0000000..072fb58 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/bg 1.imageset/bg@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/bg 1.imageset/bg@3x.png b/BeeReel/Sources/Assets.xcassets/icon/bg 1.imageset/bg@3x.png new file mode 100644 index 0000000..cc09b01 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/bg 1.imageset/bg@3x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/bg.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/bg.imageset/Contents.json new file mode 100644 index 0000000..36577cc --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/bg.imageset/bg@2x.png b/BeeReel/Sources/Assets.xcassets/icon/bg.imageset/bg@2x.png new file mode 100644 index 0000000..13d408a Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/bg.imageset/bg@2x.png differ diff --git a/BeeReel/Sources/Assets.xcassets/icon/bg.imageset/bg@3x.png b/BeeReel/Sources/Assets.xcassets/icon/bg.imageset/bg@3x.png new file mode 100644 index 0000000..c69c154 Binary files /dev/null and b/BeeReel/Sources/Assets.xcassets/icon/bg.imageset/bg@3x.png differ diff --git a/BeeReel/Sources/Localizable.xcstrings b/BeeReel/Sources/Localizable.xcstrings index 0c3dc65..41e3dfe 100644 --- a/BeeReel/Sources/Localizable.xcstrings +++ b/BeeReel/Sources/Localizable.xcstrings @@ -34,6 +34,41 @@ } } }, + "Balance: ## Coins" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Balance: ## Coins" + } + } + } + }, + "beereel_buy_fail_toast_01" : { + "comment" : "解锁失败提示", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Purchase failed, please try again later!" + } + } + } + }, + "beereel_jump_unlock_error" : { + "comment" : "解锁上一集提示", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The prequel to this series is not unlocked. Please unlock the prequel before unlocking this series" + } + } + } + }, "beereel_network" : { "extractionState" : "manual", "localizations" : { @@ -320,6 +355,17 @@ } } }, + "My Coins:" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "My Coins:" + } + } + } + }, "New Releases" : { "extractionState" : "manual", "localizations" : { @@ -474,6 +520,39 @@ } } }, + "Unlock to Continue" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unlock to Continue" + } + } + } + }, + "Unlock:" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unlock:" + } + } + } + }, + "Unlocking costs ## Coins" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unlocking costs ## Coins" + } + } + } + }, "User Agreement" : { "extractionState" : "manual", "localizations" : {