From 82ec2a370f49f332d4613870de154d50428fb627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=9C=E5=A3=B0=E4=B8=96=E7=BA=AA?= <> Date: Wed, 28 Jan 2026 16:43:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8A=E6=8A=A5=E9=98=85?= =?UTF-8?q?=E8=AF=BB=E6=97=B6=E9=95=BF=EF=BC=8C=E4=BF=AE=E5=A4=8DBUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ReaderHive.xcodeproj/project.pbxproj | 4 + .../Base/Networking/API/NRStatAPI.swift | 19 ++ ReaderHive/Base/Networking/NRTargetType.swift | 2 +- ReaderHive/Base/Networking/NRUrlPath.swift | 10 + ReaderHive/Base/VC/NRViewController.swift | 6 +- .../Class/Home/V/NRHomeNovelNextView.swift | 26 ++- ReaderHive/Class/Home/V/NRStarGradeView.swift | 2 + ReaderHive/Class/Me/V/NRMeCoinsPackView.swift | 14 +- .../Class/Novel/V/NRDetailRechargeView.swift | 2 +- .../V/NRNovelDetailHeaderView+Data.swift | 6 +- .../V/Reader/NRNovelReadBottomView.swift | 2 +- .../Reader/NRNovelReadFinishHeaderView.swift | 46 ++++- .../V/Reader/NRNovelReadStarGradeView.swift | 7 +- .../NRNovelReadFinishViewController.swift | 4 + .../NRNovelReaderViewController+Page.swift | 8 + .../VC/Read/NRNovelReaderViewController.swift | 7 + .../Novel/VM/NRNovelDetailViewModel.swift | 2 +- .../Novel/VM/NRNovelReadViewModel+Data.swift | 27 +++ .../Novel/VM/NRNovelReadViewModel+View.swift | 193 ++++++++++++++++++ .../Class/Novel/VM/NRNovelReadViewModel.swift | 185 +---------------- .../Store/V/NRCoinsPackConfirmView.swift | 2 +- ReaderHive/Class/Store/V/NRStoreVipCell.swift | 7 +- ReaderHive/Delegate/AppDelegate.swift | 8 +- ReaderHive/Libs/Alert/NRCoinsPackAlert.swift | 2 +- ReaderHive/Libs/Empty/NREmpty.swift | 1 + ReaderHive/Libs/Login/NRUserInfo.swift | 2 +- .../NovelTool/NRNovelReadSetManager.swift | 6 +- .../Source/en.lproj/Localizable.strings | 6 +- 28 files changed, 385 insertions(+), 221 deletions(-) create mode 100644 ReaderHive/Class/Novel/VM/NRNovelReadViewModel+View.swift diff --git a/ReaderHive.xcodeproj/project.pbxproj b/ReaderHive.xcodeproj/project.pbxproj index 053b1c3..e7385d1 100644 --- a/ReaderHive.xcodeproj/project.pbxproj +++ b/ReaderHive.xcodeproj/project.pbxproj @@ -90,6 +90,7 @@ 85859E5C2EF3FC5F0020D282 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 85859E552EF3FC5F0020D282 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 85859E662EF3FC6E0020D282 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85859E632EF3FC6E0020D282 /* NotificationService.swift */; }; 85859E682EFCD1D80020D282 /* NRHomeMustReadTodayTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85859E672EFCD1D80020D282 /* NRHomeMustReadTodayTransformer.swift */; }; + 85ACA3F92F28A7CD009D52B0 /* NRNovelReadViewModel+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85ACA3F82F28A7C8009D52B0 /* NRNovelReadViewModel+View.swift */; }; 85C1786B2F050AA400A8A76E /* Poppins-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 85C178682F050AA400A8A76E /* Poppins-Medium.ttf */; }; 85C1786C2F050AA400A8A76E /* Poppins-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 85C1786A2F050AA400A8A76E /* Poppins-SemiBold.ttf */; }; 85C1786D2F050AA400A8A76E /* Poppins-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 85C178672F050AA400A8A76E /* Poppins-Bold.ttf */; }; @@ -600,6 +601,7 @@ 85859E622EF3FC6E0020D282 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 85859E632EF3FC6E0020D282 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 85859E672EFCD1D80020D282 /* NRHomeMustReadTodayTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRHomeMustReadTodayTransformer.swift; sourceTree = ""; }; + 85ACA3F82F28A7C8009D52B0 /* NRNovelReadViewModel+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NRNovelReadViewModel+View.swift"; sourceTree = ""; }; 85C178672F050AA400A8A76E /* Poppins-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-Bold.ttf"; sourceTree = ""; }; 85C178682F050AA400A8A76E /* Poppins-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-Medium.ttf"; sourceTree = ""; }; 85C178692F050AA400A8A76E /* Poppins-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-Regular.ttf"; sourceTree = ""; }; @@ -1088,6 +1090,7 @@ 0373D9442ED57B7B0017DCC7 /* NRNovelDetailViewModel.swift */, F34348EF2ED8381E00AA7E70 /* NRNovelReadViewModel.swift */, F34991262EE282660039E939 /* NRNovelReadViewModel+Data.swift */, + 85ACA3F82F28A7C8009D52B0 /* NRNovelReadViewModel+View.swift */, ); path = VM; sourceTree = ""; @@ -2615,6 +2618,7 @@ 039810B62ED42D840006E317 /* NRHomeNovelNewArrivalsCell.swift in Sources */, 0373D95A2ED593D50017DCC7 /* NRSearchRecordCell.swift in Sources */, 0373D9582ED5935D0017DCC7 /* NRSearchRecordView.swift in Sources */, + 85ACA3F92F28A7CD009D52B0 /* NRNovelReadViewModel+View.swift in Sources */, F343490C2ED9751800AA7E70 /* NRReadChapterCatalogModel.swift in Sources */, F3B859862EE972F70095A9CC /* NRConsumptionRecordsCell.swift in Sources */, F34990C72EDFCE500039E939 /* NRNovelReadGradeView.swift in Sources */, diff --git a/ReaderHive/Base/Networking/API/NRStatAPI.swift b/ReaderHive/Base/Networking/API/NRStatAPI.swift index 43230fc..b38b435 100644 --- a/ReaderHive/Base/Networking/API/NRStatAPI.swift +++ b/ReaderHive/Base/Networking/API/NRStatAPI.swift @@ -162,6 +162,25 @@ struct NRStatAPI { NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in } } + + ///统计用户观看时长 + static func nr_requestWatchNovelDuration(novelId: String, duration: Int, percent: Int) { + + let parameters: [String : Any] = [ + "request_id": "\(Int(Date().timeIntervalSince1970))", + "short_play_id" : novelId, + "duration" : duration, + "percent" : percent //观看百分比 + ] + + var param = NRNetwork.Parameters(path: "/watchShortDuration") + param.method = .post + param.isLoding = false + param.isToast = false + param.parameters = parameters + + NRNetwork.request(parameters: param) { (response: NRNetwork.Response) in } + } } extension NRStatAPI { diff --git a/ReaderHive/Base/Networking/NRTargetType.swift b/ReaderHive/Base/Networking/NRTargetType.swift index d310680..ce7aa3b 100644 --- a/ReaderHive/Base/Networking/NRTargetType.swift +++ b/ReaderHive/Base/Networking/NRTargetType.swift @@ -24,7 +24,7 @@ extension NRTargetType: TargetType { var path: String { switch self { case .request(let param): - return "/readerhive" + param.path + return NRBaseURLPrefix + param.path } } diff --git a/ReaderHive/Base/Networking/NRUrlPath.swift b/ReaderHive/Base/Networking/NRUrlPath.swift index 64fdea4..899571c 100644 --- a/ReaderHive/Base/Networking/NRUrlPath.swift +++ b/ReaderHive/Base/Networking/NRUrlPath.swift @@ -7,7 +7,17 @@ import UIKit +#if DEBUG +let NRBaseURL = "https://api-novel-test.guyantv.com" +let NRBaseURLPrefix = "" +//let NRBaseURL = "https://api-readerhive.readerhive.net" +//let NRBaseURLPrefix = "/readerhive" +#else let NRBaseURL = "https://api-readerhive.readerhive.net" +let NRBaseURLPrefix = "/readerhive" +#endif + + let NRWebBaseURL = "https://www.readerhive.net" diff --git a/ReaderHive/Base/VC/NRViewController.swift b/ReaderHive/Base/VC/NRViewController.swift index 189fcc3..ed2f354 100644 --- a/ReaderHive/Base/VC/NRViewController.swift +++ b/ReaderHive/Base/VC/NRViewController.swift @@ -153,9 +153,9 @@ extension UIViewController { titleColor: UIColor = UINavigationBar.titleWhiteColor, isTranslucent: Bool = true ) { - self.navigationController?.navigationBar.nr_setTranslucent(isTranslucent: isTranslucent) - self.navigationController?.navigationBar.nr_setBackgroundColor(backgroundColor: backgroundColor) - self.navigationController?.navigationBar.nr_setTitleTextAttributes(titleTextAttributes: [ + self.navigationController?.navigationBar.nr_setTranslucent(isTranslucent) + self.navigationController?.navigationBar.nr_setBackgroundColor(backgroundColor) + self.navigationController?.navigationBar.nr_setTitleTextAttributes([ NSAttributedString.Key.font : titleFont, NSAttributedString.Key.foregroundColor : titleColor ]) diff --git a/ReaderHive/Class/Home/V/NRHomeNovelNextView.swift b/ReaderHive/Class/Home/V/NRHomeNovelNextView.swift index 6b4f746..cc7a2ae 100644 --- a/ReaderHive/Class/Home/V/NRHomeNovelNextView.swift +++ b/ReaderHive/Class/Home/V/NRHomeNovelNextView.swift @@ -22,10 +22,9 @@ class NRHomeNovelNextView: NRHomeNovelHeaderContentView { let itemHeight = 150 / 100 * itemWidth + 68 let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .horizontal - layout.itemSize = .init(width: itemWidth, height: itemHeight) - layout.minimumLineSpacing = 15 - layout.minimumInteritemSpacing = 18 + layout.itemSize = .init(width: floor(itemWidth), height: itemHeight) + layout.minimumLineSpacing = 18 + layout.minimumInteritemSpacing = 15 return layout }() @@ -36,10 +35,15 @@ class NRHomeNovelNextView: NRHomeNovelHeaderContentView { collectionView.dataSource = self collectionView.showsHorizontalScrollIndicator = false collectionView.contentInset = .init(top: 0, left: 16, bottom: 0, right: 16) + collectionView.addObserver(self, forKeyPath: "contentSize", context: nil) collectionView.register(NRHomeNovelNextViewCell.self, forCellWithReuseIdentifier: "cell") return collectionView }() + @MainActor deinit { + self.collectionView.removeObserver(self, forKeyPath: "contentSize") + } + override init(frame: CGRect) { super.init(frame: frame) self.titleLabel.text = "reader_home_title3".localized @@ -51,16 +55,26 @@ class NRHomeNovelNextView: NRHomeNovelHeaderContentView { fatalError("init(coder:) has not been implemented") } + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "contentSize" { + let height = self.collectionView.contentSize.height + 1 + + collectionView.snp.updateConstraints { make in + make.height.equalTo(height) + } + } + } + } extension NRHomeNovelNextView { - private func nr_setupUI() { + private func nr_setupUI() { contentView.addSubview(collectionView) collectionView.snp.makeConstraints { make in make.edges.equalToSuperview() - make.height.equalTo(collectionViewLayout.itemSize.height * 2 + collectionViewLayout.minimumInteritemSpacing) + make.height.equalTo(1) } } diff --git a/ReaderHive/Class/Home/V/NRStarGradeView.swift b/ReaderHive/Class/Home/V/NRStarGradeView.swift index f28ca6b..d8beb74 100644 --- a/ReaderHive/Class/Home/V/NRStarGradeView.swift +++ b/ReaderHive/Class/Home/V/NRStarGradeView.swift @@ -82,6 +82,8 @@ class NRStarGradeView: UIView { private lazy var cosmosView: CosmosView = { let view = CosmosView(settings: settings) + view.setContentHuggingPriority(.required, for: .horizontal) + view.setContentCompressionResistancePriority(.required, for: .horizontal) view.didTouchCosmos = { [weak self] rating in guard let self = self else { return } self.didTouch?(rating) diff --git a/ReaderHive/Class/Me/V/NRMeCoinsPackView.swift b/ReaderHive/Class/Me/V/NRMeCoinsPackView.swift index b3bf6c5..b00e9ce 100644 --- a/ReaderHive/Class/Me/V/NRMeCoinsPackView.swift +++ b/ReaderHive/Class/Me/V/NRMeCoinsPackView.swift @@ -21,7 +21,12 @@ class NRMeCoinsPackView: UIView { return view }() - private lazy var iconImageView = UIImageView(image: UIImage(named: "gift_icon_01")) + private lazy var iconImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "gift_icon_01")) + imageView.setContentHuggingPriority(.required, for: .horizontal) + imageView.setContentCompressionResistancePriority(.required, for: .horizontal) + return imageView + }() private lazy var titleLabel: UILabel = { let label = NRLabel() @@ -41,7 +46,12 @@ class NRMeCoinsPackView: UIView { return label }() - private lazy var indicatorImageView = UIImageView(image: UIImage(named: "arrow_right_icon_07")) + private lazy var indicatorImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "arrow_right_icon_07")) + imageView.setContentHuggingPriority(.required, for: .horizontal) + imageView.setContentCompressionResistancePriority(.required, for: .horizontal) + return imageView + }() override init(frame: CGRect) { super.init(frame: frame) diff --git a/ReaderHive/Class/Novel/V/NRDetailRechargeView.swift b/ReaderHive/Class/Novel/V/NRDetailRechargeView.swift index 28c9e98..1bb37b9 100644 --- a/ReaderHive/Class/Novel/V/NRDetailRechargeView.swift +++ b/ReaderHive/Class/Novel/V/NRDetailRechargeView.swift @@ -109,7 +109,7 @@ class NRDetailRechargeView: NRPanModalContentView { let label = UILabel() label.font = .font(ofSize: 12, weight: .regular) label.textColor = .black - label.text = "Chapter Price".localized + ":" + label.text = "reader_chapter_price".localized + ":" return label }() diff --git a/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView+Data.swift b/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView+Data.swift index 7d918aa..46ddacc 100644 --- a/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView+Data.swift +++ b/ReaderHive/Class/Novel/V/NRNovelDetailHeaderView+Data.swift @@ -115,7 +115,11 @@ extension NRNovelDetailHeaderView { } private func updateNumLabel() { - numLabel.text = NSNumber(value: num).toString(minimumFractionDigits: minimumFractionDigits) + if num < 1000 { + numLabel.text = NSNumber(value: num).toString(minimumFractionDigits: minimumFractionDigits) + } else { + numLabel.text = NSNumber(value: num).formattedNumber() + } } } diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadBottomView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadBottomView.swift index dfa1c0e..712b247 100644 --- a/ReaderHive/Class/Novel/V/Reader/NRNovelReadBottomView.swift +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadBottomView.swift @@ -32,7 +32,7 @@ class NRNovelReadBottomView: UIView { }() lazy var catalogButton: UIButton = { - let button = self.createButton(title: "Catalog".localized, icon: UIImage(named: "catalog_icon_01"), nightIcon: UIImage(named: "catalog_icon_02")) + let button = self.createButton(title: "reader_book_catalog".localized, icon: UIImage(named: "catalog_icon_01"), nightIcon: UIImage(named: "catalog_icon_02")) button.addAction(UIAction(handler: { [weak self] _ in guard let self = self else { return } self.viewModel?.showCatalogView() diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadFinishHeaderView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadFinishHeaderView.swift index da2dede..f86a633 100644 --- a/ReaderHive/Class/Novel/V/Reader/NRNovelReadFinishHeaderView.swift +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadFinishHeaderView.swift @@ -18,6 +18,13 @@ class NRNovelReadFinishHeaderView: UICollectionReusableView { } } + var didChangeContentHeight: ((_ height: CGFloat) -> Void)? + + private lazy var contentView: NRScrollView = { + let contentView = NRScrollView() + contentView.addObserver(self, forKeyPath: "contentSize", context: nil) + return contentView + }() private lazy var coverBgView: UIView = { let view = NRGradientView() @@ -55,6 +62,8 @@ class NRNovelReadFinishHeaderView: UICollectionReusableView { label.textColors = [UIColor.F_3912_F.cgColor, UIColor.FF_4_A_4_A.cgColor, UIColor.FA_9_B_1_F.cgColor] label.textStartPoint = .init(x: 0, y: 0.5) label.textEndPoint = .init(x: 1, y: 0.5) + label.numberOfLines = 0 + label.textAlignment = .center label.text = "reader_book_finished".localized return label }() @@ -84,10 +93,14 @@ class NRNovelReadFinishHeaderView: UICollectionReusableView { let label = UILabel() label.font = .font(ofSize: 16, weight: .semibold) label.textColor = .black - label.text = "read_finish_list_title".localized + label.text = "reader_book_finished_list_title".localized return label }() + deinit { + self.contentView.removeObserver(self, forKeyPath: "contentSize") + } + override init(frame: CGRect) { super.init(frame: frame) nr_setupUI() @@ -103,19 +116,31 @@ class NRNovelReadFinishHeaderView: UICollectionReusableView { fatalError("init(coder:) has not been implemented") } + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "contentSize" { + let height = self.contentView.contentSize.height + 1 + self.didChangeContentHeight?(height) + } + } + } extension NRNovelReadFinishHeaderView { private func nr_setupUI() { - addSubview(coverBgView) + addSubview(contentView) + contentView.addSubview(coverBgView) coverBgView.addSubview(coverImageView) - addSubview(coverDecorateView) - addSubview(titleLabel) - addSubview(textLabel) - addSubview(gradeView) - addSubview(lineView) - addSubview(listTitleLabel) + contentView.addSubview(coverDecorateView) + contentView.addSubview(titleLabel) + contentView.addSubview(textLabel) + contentView.addSubview(gradeView) + contentView.addSubview(lineView) + contentView.addSubview(listTitleLabel) + + contentView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } coverBgView.snp.makeConstraints { make in make.centerX.equalToSuperview() @@ -138,12 +163,14 @@ extension NRNovelReadFinishHeaderView { titleLabel.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalTo(coverBgView.snp.bottom).offset(20) +// make.right.lessThanOrEqualToSuperview().offset(-16) + make.right.lessThanOrEqualTo(gradeView) } textLabel.snp.makeConstraints { make in make.centerX.equalToSuperview() - make.right.lessThanOrEqualToSuperview().offset(16) make.top.equalTo(titleLabel.snp.bottom).offset(6) + make.right.lessThanOrEqualTo(gradeView) } gradeView.snp.makeConstraints { make in @@ -162,6 +189,7 @@ extension NRNovelReadFinishHeaderView { listTitleLabel.snp.makeConstraints { make in make.left.equalToSuperview().offset(16) make.top.equalTo(lineView.snp.bottom).offset(16) + make.bottom.equalToSuperview().offset(-16) } } diff --git a/ReaderHive/Class/Novel/V/Reader/NRNovelReadStarGradeView.swift b/ReaderHive/Class/Novel/V/Reader/NRNovelReadStarGradeView.swift index eb2ab54..b4e22f3 100644 --- a/ReaderHive/Class/Novel/V/Reader/NRNovelReadStarGradeView.swift +++ b/ReaderHive/Class/Novel/V/Reader/NRNovelReadStarGradeView.swift @@ -38,6 +38,7 @@ class NRNovelReadStarGradeView: UIView { let label = UILabel() label.font = .font(ofSize: 12, weight: .medium) label.textColor = .black + label.numberOfLines = 0 return label }() @@ -46,8 +47,11 @@ class NRNovelReadStarGradeView: UIView { view.filledImage = UIImage(named: "star_icon_04") view.emptyImage = UIImage(named: "star_icon_05") view.updateOnTouch = true - view.fillMode = .full +// view.fillMode = .full + view.fillMode = .precise view.didFinishTouching = self.didFinishTouchingGrade + view.setContentHuggingPriority(.required, for: .horizontal) + view.setContentCompressionResistancePriority(.required, for: .horizontal) return view }() @@ -102,6 +106,7 @@ extension NRNovelReadStarGradeView { label.snp.makeConstraints { make in make.centerY.equalToSuperview() + make.right.lessThanOrEqualTo(gradeView.snp.left).offset(-10) make.left.equalToSuperview().offset(12) } diff --git a/ReaderHive/Class/Novel/VC/Read/NRNovelReadFinishViewController.swift b/ReaderHive/Class/Novel/VC/Read/NRNovelReadFinishViewController.swift index 214ebca..0c08cd6 100644 --- a/ReaderHive/Class/Novel/VC/Read/NRNovelReadFinishViewController.swift +++ b/ReaderHive/Class/Novel/VC/Read/NRNovelReadFinishViewController.swift @@ -119,6 +119,10 @@ extension NRNovelReadFinishViewController: UICollectionViewDelegate, UICollectio func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath) as! NRNovelReadFinishHeaderView + view.didChangeContentHeight = { [weak self] height in + guard let self = self else { return } + self.collectionViewLayout.headerReferenceSize = .init(width: UIScreen.width, height: height) + } view.novelModel = self.viewModel?.novelModel return view } diff --git a/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController+Page.swift b/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController+Page.swift index 82c311e..78988fc 100644 --- a/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController+Page.swift +++ b/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController+Page.swift @@ -50,6 +50,7 @@ extension NRNovelReaderViewController { if let vc = viewController as? NRNovelReadBaseViewController { let model = vc.catalogModel + let pageModel = vc.pageModel let lockCoins = model?.coins ?? 0 let myCoins = NRLoginManager.manager.userInfo?.totalCoins ?? 0 if model?.is_lock == true { @@ -59,6 +60,13 @@ extension NRNovelReaderViewController { self.viewModel.openRechargeView() } } + + if pageModel?.pageType != .textPart { + self.viewModel.statWatchNovelDuration() + } else { + self.viewModel.startWatchStartDate() + } + } } diff --git a/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController.swift b/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController.swift index bf6356d..e799a4f 100644 --- a/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController.swift +++ b/ReaderHive/Class/Novel/VC/Read/NRNovelReaderViewController.swift @@ -114,6 +114,8 @@ class NRNovelReaderViewController: NRViewController { UIApplication.shared.isIdleTimerDisabled = true UIScreen.main.brightness = NRNovelReadSetManager.manager.brightness + + self.viewModel.startWatchStartDate() } override func viewWillDisappear(_ animated: Bool) { @@ -124,6 +126,7 @@ class NRNovelReaderViewController: NRViewController { override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) UIScreen.main.brightness = oldBrightness + self.viewModel.statWatchNovelDuration() } override var prefersStatusBarHidden: Bool { @@ -217,6 +220,8 @@ extension NRNovelReaderViewController { ///装载数据 func loadData() { Task { + self.viewModel.statWatchNovelDuration() + NRHud.show() //获取小说数据 await self.viewModel.requestNovelDetail() @@ -324,11 +329,13 @@ extension NRNovelReaderViewController { @objc private func willResignActiveNotification() { guard self.isViewDidAppear else { return } UIScreen.main.brightness = oldBrightness + self.viewModel.statWatchNovelDuration() } @objc private func didBecomeActiveNotification() { guard self.isViewDidAppear else { return } UIScreen.main.brightness = NRNovelReadSetManager.manager.brightness + self.viewModel.startWatchStartDate() } } diff --git a/ReaderHive/Class/Novel/VM/NRNovelDetailViewModel.swift b/ReaderHive/Class/Novel/VM/NRNovelDetailViewModel.swift index bb60b56..2844b37 100644 --- a/ReaderHive/Class/Novel/VM/NRNovelDetailViewModel.swift +++ b/ReaderHive/Class/Novel/VM/NRNovelDetailViewModel.swift @@ -16,7 +16,7 @@ class NRNovelDetailViewModel: NSObject { var recommandDataArr: [NRNovelModel]? func requestDetailData() async { - let (model, id, msg) = await NRNovelAPI.requestDetail(novelId) + let (model, _, _) = await NRNovelAPI.requestDetail(novelId) await MainActor.run { if let model = model { diff --git a/ReaderHive/Class/Novel/VM/NRNovelReadViewModel+Data.swift b/ReaderHive/Class/Novel/VM/NRNovelReadViewModel+Data.swift index c401a16..27fa9df 100644 --- a/ReaderHive/Class/Novel/VM/NRNovelReadViewModel+Data.swift +++ b/ReaderHive/Class/Novel/VM/NRNovelReadViewModel+Data.swift @@ -286,6 +286,33 @@ extension NRNovelReadViewModel { } } + func startWatchStartDate() { + let (model, pageModel) = self.getCurrentPageData() + guard let model = model else { return } + guard let pageModel = pageModel else { return } + if model.is_lock == false, self.watchStartDate == nil, pageModel.pageType == .textPart { + if self.vc?.isViewDidAppear == true { + self.watchStartDate = Date() + } + } + } + ///统计观看时长 + func statWatchNovelDuration() { + guard let watchStartDate = self.watchStartDate else { return } + self.watchStartDate = nil + let (catalogModel, _) = getCurrentPageData() + +// guard let chapterModel = catalogModel?.chapterModel else { return } + let totalEp = CGFloat(self.novelModel?.episode_total ?? 1) +// let currentEp = CGFloat(NSNumber(pointer: chapterModel.episode ?? "0").floatValue) + let currentEp = CGFloat(self.currentPageIndexPath.section) + + let nowDate = Date() + let duration = Int(nowDate.timeIntervalSince1970 - watchStartDate.timeIntervalSince1970) + let percent = currentEp / totalEp * 100 + + NRStatAPI.nr_requestWatchNovelDuration(novelId: self.novelId, duration: duration, percent: Int(percent)) + } } diff --git a/ReaderHive/Class/Novel/VM/NRNovelReadViewModel+View.swift b/ReaderHive/Class/Novel/VM/NRNovelReadViewModel+View.swift new file mode 100644 index 0000000..e3a8394 --- /dev/null +++ b/ReaderHive/Class/Novel/VM/NRNovelReadViewModel+View.swift @@ -0,0 +1,193 @@ +// +// NRNovelReadViewModel+View.swift +// ReaderHive +// +// Created by 澜声世纪 on 2026/1/27. +// + +import UIKit +import HWPanModal +import YYCategories + +extension NRNovelReadViewModel { + + ///退出阅读页面 + func backReadPage() { + Task { + guard let isShowModel = await NRNovelAPI.requestShowRecommendPop(id: self.novelId), isShowModel.is_pop_up == true else { + _backReadPage() + return + } + + await MainActor.run { + let alert = NRAlert(title: "reader_recommend_book".localized, detail: "reader_recommend_book_tip".localized, topIconImage: UIImage(named: "alert_top_icon_01"), highlightButtonText: "reader_recommend_book_ok".localized) + alert.closeHandle = { [weak self] in + guard let self = self else { return } + self._backReadPage() + } + alert.highlightHandle = { [weak self] in + guard let self = self else { return } + NRNovelAPI.requestConfirmRecommend(self.novelId) + self._backReadPage() + } + alert.show() + } + } + + + } + + @MainActor + private func _backReadPage() { + self.vc?.navigationController?.popViewController(animated: true) + } + + func showAllMenuView(isShow: Bool) { + self.showTopView(isShow: isShow) + self.showBottomView(isShow: isShow) + } + + ///显示顶部视图 + func showTopView(isShow: Bool) { + guard showTop != isShow else { return } + self.showTop = isShow + self.vc?.setNeedsStatusBarAppearanceUpdate() + + UIView.animate(withDuration: NRNovelReadSetManager.manager.animateDuration) { [weak self] in + guard let self = self else { return } + if isShow { + self.topView?.transform = CGAffineTransform(translationX: 0, y: NRNovelReadSetManager.manager.topViewHeight) + } else { + self.topView?.transform = CGAffineTransform.identity + } + } completion: { _ in + + } + } + + ///显示底部视图 + func showBottomView(isShow: Bool) { + + UIView.animate(withDuration: NRNovelReadSetManager.manager.animateDuration) { [weak self] in + guard let self = self else { return } + if isShow { + self.bottomView?.transform = CGAffineTransform(translationX: 0, y: -NRNovelReadSetManager.manager.bottomViewHeight) + } else { + self.bottomView?.transform = CGAffineTransform.identity + } + } completion: { _ in + + } + } + + ///显示更多工具栏 + func showMoreView() { + guard let model = self.novelModel else { return } + + let view = NRNovelReadGradeView() + view.model = model + view.present(in: nil) + } + + ///展示设置页面 + func showSettingView() { + + let view = NRNovelReadSettingView() + view.present(in: nil) + } + + ///展示目录 + func showCatalogView() { + let (catalogModel, _) = self.getCurrentPageData() + + let view = NRNovelReaderCatalogView() + view.novelModel = self.novelModel + view.currentCatalogModel = catalogModel + view.catalogDataArr = self.chapterCatalogList + view.didSelected = { [weak self] index in + guard let self = self else { return } + if index != self.currentPageIndexPath.section { + self.skip(chapterIndex: index) + } + } + view.show() + } + + ///打开充值页面 + func openRechargeView() { + guard self.popView == nil else { return } + let (catalogModel, _) = self.getCurrentPageData() + guard let catalogModel = catalogModel else { return } + self.statWatchNovelDuration() + + self.payDataRequest = NRPayDataRequest() + if let model = NRIapManager.manager.payDateModel { + _openRechargeView(model, catalogModel) + self.payDataRequest?.requestProducts(isLoding: false) { [weak self] model in + guard let self = self else { return } + guard let model = model else { return } + if let view = self.popView as? NRDetailRechargeView { + view.payModel = model + } + } + } else { + self.payDataRequest?.requestProducts(isLoding: true) { [weak self] model in + guard let self = self else { return } + guard let model = model else { return } + self._openRechargeView(model, catalogModel) + } + } + } + + private func _openRechargeView(_ payModel: NRPayDateModel, _ catalogModel: NRReadChapterCatalogModel) { + guard self.popView == nil else { return } + + NRStatAPI.nr_requestEventStat(orderCode: nil, shortPlayId: self.novelId, videoId: catalogModel.id, eventKey: .payTemplateDialog, errorMsg: nil, otherParamenters: [ + "event_name" : "pay open" + ]) + + let view = NRDetailRechargeView() + view.price = catalogModel.coins + view.payModel = payModel + view.worksId = self.novelId + view.chapterId = catalogModel.id + + view.buyFinishHandle = { [weak self] in + guard let self = self else { return } + self.targetCatalogModel = catalogModel + self.vc?.loadData() + } + +// view.didDismissHandle = { [weak self] in +// guard let self = self else { return } +// self.showVipRetainAlert(catalogModel) +// } + view.present(in: nil) + + self.popView = view + } + + ///展示挽留弹窗 + private func showVipRetainAlert(_ catalogModel: NRReadChapterCatalogModel) { + payDataRequest = NRPayDataRequest() + + payDataRequest?.requestVipRetainPayInfo { [weak self] model in + guard let self = self else { return } + guard let model = model else { return } + + let view = NRVipRetainAlert() + view.model = model + view.worksId = self.novelId + view.chapterId = catalogModel.id + view.buyFinishHandle = { [weak self] in + guard let self = self else { return } + self.targetCatalogModel = catalogModel + self.vc?.loadData() + } + view.show(in: nil) + } + + + } + +} diff --git a/ReaderHive/Class/Novel/VM/NRNovelReadViewModel.swift b/ReaderHive/Class/Novel/VM/NRNovelReadViewModel.swift index 1bd8e83..b5b0751 100644 --- a/ReaderHive/Class/Novel/VM/NRNovelReadViewModel.swift +++ b/ReaderHive/Class/Novel/VM/NRNovelReadViewModel.swift @@ -27,7 +27,7 @@ class NRNovelReadViewModel: NSObject { } } - private(set) var showTop = false + var showTop = false ///章节目录列表 lazy var chapterCatalogList: [NRReadChapterCatalogModel] = [] @@ -49,190 +49,13 @@ class NRNovelReadViewModel: NSObject { weak var popView: UIView? - private var payDataRequest = NRPayDataRequest() + var payDataRequest: NRPayDataRequest? + ///用来统计观看时长 + var watchStartDate: Date? } -extension NRNovelReadViewModel { - - ///退出阅读页面 - func backReadPage() { - Task { - guard let isShowModel = await NRNovelAPI.requestShowRecommendPop(id: self.novelId), isShowModel.is_pop_up == true else { - _backReadPage() - return - } - - await MainActor.run { - let alert = NRAlert(title: "reader_recommend_book".localized, detail: "reader_recommend_book_tip".localized, topIconImage: UIImage(named: "alert_top_icon_01"), highlightButtonText: "reader_recommend_book_ok".localized) - alert.closeHandle = { [weak self] in - guard let self = self else { return } - self._backReadPage() - } - alert.highlightHandle = { [weak self] in - guard let self = self else { return } - NRNovelAPI.requestConfirmRecommend(self.novelId) - self._backReadPage() - } - alert.show() - } - } - - - } - - @MainActor - private func _backReadPage() { - self.vc?.navigationController?.popViewController(animated: true) - } - - func showAllMenuView(isShow: Bool) { - self.showTopView(isShow: isShow) - self.showBottomView(isShow: isShow) - } - - ///显示顶部视图 - func showTopView(isShow: Bool) { - guard showTop != isShow else { return } - self.showTop = isShow - self.vc?.setNeedsStatusBarAppearanceUpdate() - - UIView.animate(withDuration: NRNovelReadSetManager.manager.animateDuration) { [weak self] in - guard let self = self else { return } - if isShow { - self.topView?.transform = CGAffineTransform(translationX: 0, y: NRNovelReadSetManager.manager.topViewHeight) - } else { - self.topView?.transform = CGAffineTransform.identity - } - } completion: { _ in - - } - } - - ///显示底部视图 - func showBottomView(isShow: Bool) { - - UIView.animate(withDuration: NRNovelReadSetManager.manager.animateDuration) { [weak self] in - guard let self = self else { return } - if isShow { - self.bottomView?.transform = CGAffineTransform(translationX: 0, y: -NRNovelReadSetManager.manager.bottomViewHeight) - } else { - self.bottomView?.transform = CGAffineTransform.identity - } - } completion: { _ in - - } - } - - ///显示更多工具栏 - func showMoreView() { - guard let model = self.novelModel else { return } - - let view = NRNovelReadGradeView() - view.model = model - view.present(in: nil) - } - - ///展示设置页面 - func showSettingView() { - - let view = NRNovelReadSettingView() - view.present(in: nil) - } - - ///展示目录 - func showCatalogView() { - let (catalogModel, _) = self.getCurrentPageData() - - let view = NRNovelReaderCatalogView() - view.novelModel = self.novelModel - view.currentCatalogModel = catalogModel - view.catalogDataArr = self.chapterCatalogList - view.didSelected = { [weak self] index in - guard let self = self else { return } - if index != self.currentPageIndexPath.section { - self.skip(chapterIndex: index) - } - } - view.show() - } - - ///打开充值页面 - func openRechargeView() { - guard self.popView == nil else { return } - let (catalogModel, _) = self.getCurrentPageData() - guard let catalogModel = catalogModel else { return } - - if let model = NRIapManager.manager.payDateModel { - _openRechargeView(model, catalogModel) - - self.payDataRequest.requestProducts(isLoding: false) { [weak self] model in - guard let self = self else { return } - guard let model = model else { return } - if let view = self.popView as? NRDetailRechargeView { - view.payModel = model - } - } - } else { - self.payDataRequest.requestProducts(isLoding: true) { [weak self] model in - guard let self = self else { return } - guard let model = model else { return } - self._openRechargeView(model, catalogModel) - } - } - } - - private func _openRechargeView(_ payModel: NRPayDateModel, _ catalogModel: NRReadChapterCatalogModel) { - guard self.popView == nil else { return } - - NRStatAPI.nr_requestEventStat(orderCode: nil, shortPlayId: self.novelId, videoId: catalogModel.id, eventKey: .payTemplateDialog, errorMsg: nil, otherParamenters: [ - "event_name" : "pay open" - ]) - - let view = NRDetailRechargeView() - view.price = catalogModel.coins - view.payModel = payModel - view.worksId = self.novelId - view.chapterId = catalogModel.id - - view.buyFinishHandle = { [weak self] in - guard let self = self else { return } - self.targetCatalogModel = catalogModel - self.vc?.loadData() - } - -// view.didDismissHandle = { [weak self] in -// guard let self = self else { return } -// self.showVipRetainAlert(catalogModel) -// } - view.present(in: nil) - - self.popView = view - } - - ///展示挽留弹窗 - private func showVipRetainAlert(_ catalogModel: NRReadChapterCatalogModel) { - - payDataRequest.requestVipRetainPayInfo { [weak self] model in - guard let self = self else { return } - guard let model = model else { return } - - let view = NRVipRetainAlert() - view.model = model - view.worksId = self.novelId - view.chapterId = catalogModel.id - view.buyFinishHandle = { [weak self] in - guard let self = self else { return } - self.targetCatalogModel = catalogModel - self.vc?.loadData() - } - view.show(in: nil) - } - - - } -} //MARK: UIGestureRecognizerDelegate extension NRNovelReadViewModel: UIGestureRecognizerDelegate { diff --git a/ReaderHive/Class/Store/V/NRCoinsPackConfirmView.swift b/ReaderHive/Class/Store/V/NRCoinsPackConfirmView.swift index 1c2a056..6efcaf3 100644 --- a/ReaderHive/Class/Store/V/NRCoinsPackConfirmView.swift +++ b/ReaderHive/Class/Store/V/NRCoinsPackConfirmView.swift @@ -85,7 +85,7 @@ class NRCoinsPackConfirmView: NRPanModalContentView { var configuration = UIButton.Configuration.plain() configuration.background.image = UIImage(named: "gradient_color_01") configuration.background.cornerRadius = 24 - configuration.attributedTitle = AttributedString("Continue".localized, attributes: AttributeContainer([ + configuration.attributedTitle = AttributedString("reader_continue".localized, attributes: AttributeContainer([ .font : UIFont.font(ofSize: 14, weight: .bold), .foregroundColor : UIColor.white ])) diff --git a/ReaderHive/Class/Store/V/NRStoreVipCell.swift b/ReaderHive/Class/Store/V/NRStoreVipCell.swift index fb5c3d1..207ea5b 100644 --- a/ReaderHive/Class/Store/V/NRStoreVipCell.swift +++ b/ReaderHive/Class/Store/V/NRStoreVipCell.swift @@ -15,7 +15,8 @@ class NRStoreVipCell: UICollectionViewCell { var model: NRPayItem? { didSet { titleLabel.text = model?.brief - desLabel.text = model?.nr_description +// desLabel.text = model?.nr_description + desLabel.text = "reader_my_vip_tips".localized if let coins = model?.send_coins, coins > 0 { extraLabel.text = "+\(coins) \("reader_extra".localized)" @@ -35,10 +36,6 @@ class NRStoreVipCell: UICollectionViewCell { } if let price = offerPrice { -// let priceString = NSMutableAttributedString(string: currency + price) -// priceString.yy_font = .font(ofSize: 28, weight: .init(800)) -// priceLabel.attributedText = priceString -// let oldPriceStr = NSMutableAttributedString(string: oldPrice) oldPriceStr.yy_strikethroughColor = oldPriceLabel.textColor oldPriceStr.yy_strikethroughStyle = .double diff --git a/ReaderHive/Delegate/AppDelegate.swift b/ReaderHive/Delegate/AppDelegate.swift index 95421af..a1c7a0e 100644 --- a/ReaderHive/Delegate/AppDelegate.swift +++ b/ReaderHive/Delegate/AppDelegate.swift @@ -18,6 +18,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { nr_registThirdparty(application, didFinishLaunchingWithOptions: launchOptions) NotificationCenter.default.addObserver(self, selector: #selector(networkStatusDidChangeNotification), name: NRNetworkReachableManager.networkStatusDidChangeNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(localizedDidChangeNotification), name: NRLocalizedManager.localizedDidChangeNotification, object: nil) setConfig() @@ -50,7 +51,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { self.refreshAppData() } - + + @objc private func localizedDidChangeNotification() { + self.refreshAppData() + } } private extension AppDelegate { @@ -63,5 +67,7 @@ private extension AppDelegate { NRIapManager.manager.preloadingProducts() } + + } diff --git a/ReaderHive/Libs/Alert/NRCoinsPackAlert.swift b/ReaderHive/Libs/Alert/NRCoinsPackAlert.swift index a8608d7..d46c40f 100644 --- a/ReaderHive/Libs/Alert/NRCoinsPackAlert.swift +++ b/ReaderHive/Libs/Alert/NRCoinsPackAlert.swift @@ -21,7 +21,7 @@ class NRCoinsPackAlert: NRAlert { titleLabel.numberOfLines = 0 titleLabel.font = .font(ofSize: 20, weight: .medium) titleLabel.textColor = .F_9710_D - titleLabel.text = "coins_pack_alert_title".localized + titleLabel.text = "reader_my_daily_reward".localized let coinsView = UIView() diff --git a/ReaderHive/Libs/Empty/NREmpty.swift b/ReaderHive/Libs/Empty/NREmpty.swift index 9dd46c2..bab19f6 100644 --- a/ReaderHive/Libs/Empty/NREmpty.swift +++ b/ReaderHive/Libs/Empty/NREmpty.swift @@ -33,6 +33,7 @@ struct NREmpty { view?.actionBtnBackGroundColor = .clear view?.actionBtnMargin = 16 view?.actionBtnHorizontalMargin = 25 + view?.actionBtnWidth = 0 return view! diff --git a/ReaderHive/Libs/Login/NRUserInfo.swift b/ReaderHive/Libs/Login/NRUserInfo.swift index d56d87b..d3685a1 100644 --- a/ReaderHive/Libs/Login/NRUserInfo.swift +++ b/ReaderHive/Libs/Login/NRUserInfo.swift @@ -26,7 +26,7 @@ class NRUserInfo: NSObject, SmartCodable, NSSecureCoding { if let name = family_name, !name.isEmpty { return name } else { - return "Visitor" + return "reader_visitor".localized } } diff --git a/ReaderHive/Libs/NovelTool/NRNovelReadSetManager.swift b/ReaderHive/Libs/NovelTool/NRNovelReadSetManager.swift index af6bd18..ca7748d 100644 --- a/ReaderHive/Libs/NovelTool/NRNovelReadSetManager.swift +++ b/ReaderHive/Libs/NovelTool/NRNovelReadSetManager.swift @@ -62,11 +62,11 @@ class NRNovelReadSetManager: NSObject { var paragraphSpacing: CGFloat { switch self.readSet.paragraphSpacingType { case .small: - return 15 + return 6 case .standard: - return 17 + return 8 case .large: - return 19 + return 10 } } diff --git a/ReaderHive/Source/en.lproj/Localizable.strings b/ReaderHive/Source/en.lproj/Localizable.strings index 694d056..574fa95 100644 --- a/ReaderHive/Source/en.lproj/Localizable.strings +++ b/ReaderHive/Source/en.lproj/Localizable.strings @@ -149,13 +149,15 @@ "reader_open_notification_later" = "Later"; "reader_default" = "Default"; "reader_login" = "Log in"; -"Chapter Price" = "Chapter Price"; +"reader_chapter_price" = "Chapter Price"; +"reader_my_vip_tips" = "Unrestricted access to all series!"; +"reader_visitor" = "Visitor"; "vip_retain_alert_text" = "Unlock every show you love!"; "reader_log_out_content" = "Are you sure you want to log out?"; "detail_collect_alert_title" = "Delete This Book?"; "detail_collect_alert_text" = "Confirm to delete this book from the list"; -"coins_pack_alert_title" = "Daily Reward is waiting!"; +"reader_my_daily_reward" = "Daily Reward is waiting!"; "reader_open_notification" = "Enable Notifications"; "reader_open_notification_info" = "Stay informed with popular recommendations and latest updates!";