阅读页面增加推荐,阅读完成页面优化

This commit is contained in:
澜声世纪 2026-02-05 13:35:54 +08:00
parent 5d55036f74
commit 5e58cefcd0
42 changed files with 1151 additions and 221 deletions

View File

@ -93,6 +93,11 @@
85ACA3F92F28A7CD009D52B0 /* NRNovelReadViewModel+View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85ACA3F82F28A7C8009D52B0 /* NRNovelReadViewModel+View.swift */; };
85B5B3982F30732800700E83 /* NRPayRetainAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B5B3972F30732800700E83 /* NRPayRetainAlert.swift */; };
85B5B39A2F30790600700E83 /* NRBorderLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B5B3992F30790600700E83 /* NRBorderLabel.swift */; };
85B5B39C2F31DAC100700E83 /* NRNovelReadRecommendViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B5B39B2F31DAC100700E83 /* NRNovelReadRecommendViewController.swift */; };
85B5B39E2F31F36100700E83 /* NRNovelReadFinishInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B5B39D2F31F36100700E83 /* NRNovelReadFinishInfoView.swift */; };
85B5B3A02F331D8700700E83 /* NRNovelReadRecommendCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B5B39F2F331D8700700E83 /* NRNovelReadRecommendCell.swift */; };
85B5B3A22F34378600700E83 /* NRNovelReadFinishTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B5B3A12F34378600700E83 /* NRNovelReadFinishTextCell.swift */; };
85B5B3A42F3445BC00700E83 /* NRNovelReadFinishFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B5B3A32F3445BC00700E83 /* NRNovelReadFinishFooterView.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 */; };
@ -606,6 +611,11 @@
85ACA3F82F28A7C8009D52B0 /* NRNovelReadViewModel+View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NRNovelReadViewModel+View.swift"; sourceTree = "<group>"; };
85B5B3972F30732800700E83 /* NRPayRetainAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRPayRetainAlert.swift; sourceTree = "<group>"; };
85B5B3992F30790600700E83 /* NRBorderLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRBorderLabel.swift; sourceTree = "<group>"; };
85B5B39B2F31DAC100700E83 /* NRNovelReadRecommendViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadRecommendViewController.swift; sourceTree = "<group>"; };
85B5B39D2F31F36100700E83 /* NRNovelReadFinishInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadFinishInfoView.swift; sourceTree = "<group>"; };
85B5B39F2F331D8700700E83 /* NRNovelReadRecommendCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadRecommendCell.swift; sourceTree = "<group>"; };
85B5B3A12F34378600700E83 /* NRNovelReadFinishTextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadFinishTextCell.swift; sourceTree = "<group>"; };
85B5B3A32F3445BC00700E83 /* NRNovelReadFinishFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NRNovelReadFinishFooterView.swift; sourceTree = "<group>"; };
85C178672F050AA400A8A76E /* Poppins-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-Bold.ttf"; sourceTree = "<group>"; };
85C178682F050AA400A8A76E /* Poppins-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-Medium.ttf"; sourceTree = "<group>"; };
85C178692F050AA400A8A76E /* Poppins-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-Regular.ttf"; sourceTree = "<group>"; };
@ -2189,6 +2199,10 @@
F34990C62EDFCE500039E939 /* NRNovelReadGradeView.swift */,
F34990FC2EE124CF0039E939 /* NRNovelReadStarGradeView.swift */,
F34990F82EE118FC0039E939 /* NRNovelReadFinishHeaderView.swift */,
85B5B3A32F3445BC00700E83 /* NRNovelReadFinishFooterView.swift */,
85B5B39D2F31F36100700E83 /* NRNovelReadFinishInfoView.swift */,
85B5B39F2F331D8700700E83 /* NRNovelReadRecommendCell.swift */,
85B5B3A12F34378600700E83 /* NRNovelReadFinishTextCell.swift */,
);
path = Reader;
sourceTree = "<group>";
@ -2202,6 +2216,7 @@
F34990F42EE0346B0039E939 /* NRNovelReadBaseViewController.swift */,
F34348F82ED855AA00AA7E70 /* NRNovelReadContentViewController.swift */,
F34990F22EE02FD60039E939 /* NRNovelReadFinishViewController.swift */,
85B5B39B2F31DAC100700E83 /* NRNovelReadRecommendViewController.swift */,
);
path = Read;
sourceTree = "<group>";
@ -2617,6 +2632,7 @@
85606A922EEBB336005D989D /* NRCoinsPackConfirmItemView.swift in Sources */,
0398106A2ED0505D0006E317 /* NRNetworkReachableManager.swift in Sources */,
F34348C32ED6A20700AA7E70 /* NRExploreNovelContentViewController.swift in Sources */,
85B5B3A02F331D8700700E83 /* NRNovelReadRecommendCell.swift in Sources */,
85CF94232EEC08F4006467E3 /* NRCoinsPackClaimView.swift in Sources */,
F34991292EE285660039E939 /* NRShowRecommendPop.swift in Sources */,
0373D9642ED5ABBC0017DCC7 /* NREmpty.swift in Sources */,
@ -2684,6 +2700,7 @@
F3B859892EE97E1F0095A9CC /* NRRewardCoinsViewController.swift in Sources */,
85CF945C2EF28433006467E3 /* NROpenAppModel.swift in Sources */,
039810CE2ED47A130006E317 /* CGMutablePath+NRRoundedCorner.swift in Sources */,
85B5B39C2F31DAC100700E83 /* NRNovelReadRecommendViewController.swift in Sources */,
F34348E72ED7F91C00AA7E70 /* NSNumber+NRAdd.swift in Sources */,
F3B8597F2EE96F810095A9CC /* NRConsumptionRecordsViewController.swift in Sources */,
F34349122EDA84F100AA7E70 /* NRProgressView.swift in Sources */,
@ -2836,6 +2853,7 @@
85CF95F92EF3CC93006467E3 /* GAMRecommendThimraView.swift in Sources */,
85CF95FA2EF3CC93006467E3 /* EMEditView.swift in Sources */,
85CF95FB2EF3CC93006467E3 /* ITBaseController.swift in Sources */,
85B5B3A22F34378600700E83 /* NRNovelReadFinishTextCell.swift in Sources */,
85CF95FC2EF3CC93006467E3 /* REMustManagerCell.swift in Sources */,
85CF95FD2EF3CC93006467E3 /* ECenterClaimView.swift in Sources */,
85CF95FE2EF3CC93006467E3 /* AGWElyonResult.swift in Sources */,
@ -2905,6 +2923,7 @@
85CF963E2EF3CC93006467E3 /* NCYAterfallUnechoView.swift in Sources */,
85CF963F2EF3CC93006467E3 /* DLTitleView.swift in Sources */,
85CF96402EF3CC93006467E3 /* JVibeoView.swift in Sources */,
85B5B39E2F31F36100700E83 /* NRNovelReadFinishInfoView.swift in Sources */,
85CF96412EF3CC93006467E3 /* CJBbfdebaffdController.swift in Sources */,
85CF96422EF3CC93006467E3 /* QMTextModuleCell.swift in Sources */,
85CF96432EF3CC93006467E3 /* BSModityAshed.swift in Sources */,
@ -2914,6 +2933,7 @@
85CF96472EF3CC93006467E3 /* DRegisterEdit.swift in Sources */,
85CF96482EF3CC93006467E3 /* LYWindowLocalizableView.swift in Sources */,
85CF96492EF3CC93006467E3 /* HKIBrightnessView.swift in Sources */,
85B5B3A42F3445BC00700E83 /* NRNovelReadFinishFooterView.swift in Sources */,
85CF964A2EF3CC93006467E3 /* BMGNovelView.swift in Sources */,
F34349282EDD815D00AA7E70 /* NRNovelCatalogCell.swift in Sources */,
039810D22ED54F190006E317 /* NRHomeNovelListTextCell.swift in Sources */,

View File

@ -261,6 +261,46 @@ struct NRNovelAPI {
}
}
static func requestReadFinishRecommand(novelId: String) async -> NRNovelModel? {
await withCheckedContinuation { continuation in
var param = NRNetwork.Parameters(path: "/novel/getRecommandOneDetails")
param.method = .get
param.parameters = [
"short_play_id" : novelId,
]
NRNetwork.request(parameters: param) { (response: NRNetwork.Response<NRNovelModel>) in
if response.isSuccess {
continuation.resume(returning: response.data)
} else {
continuation.resume(returning: nil)
}
}
}
}
static func requestReadRecommand(type: Int) async -> [NRNovelModel]? {
await withCheckedContinuation { continuation in
var param = NRNetwork.Parameters(path: "/novel/getTypeRecommand")
param.method = .get
param.parameters = [
"type" : type,
]
NRNetwork.request(parameters: param) { (response: NRNetwork.Response<NRNetwork.List<NRNovelModel>>) in
if response.isSuccess {
continuation.resume(returning: response.data?.list)
} else {
continuation.resume(returning: nil)
}
}
}
}
}

View File

@ -8,10 +8,10 @@
import UIKit
#if DEBUG
//let NRBaseURL = "https://api-novel-test.guyantv.com"
//let NRBaseURLPrefix = ""
let NRBaseURL = "https://api-readerhive.readerhive.net"
let NRBaseURLPrefix = "/readerhive"
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"

View File

@ -30,6 +30,13 @@ class NRStarGradeView: UIView {
}
}
var textColor = UIColor.black {
didSet {
settings.textColor = textColor
cosmosView.settings = settings
}
}
var filledImage: UIImage? = UIImage(named: "star_icon_02") {
didSet {
settings.filledImage = filledImage
@ -73,7 +80,7 @@ class NRStarGradeView: UIView {
settings.starMargin = 4
settings.fillMode = fillMode
settings.textFont = .font(ofSize: 12, weight: .regular)
settings.textColor = .black
settings.textColor = textColor
settings.textMargin = 4
settings.minTouchRating = 1
settings.updateOnTouch = true

View File

@ -51,6 +51,8 @@ class NRNovelModel: NSObject, SmartCodable {
var progress: NRNovelReadRecordModel?
var chapterInfo: NRReadChapterModel?
static func mappingForKey() -> [SmartKeyTransformer]? {
return [

View File

@ -27,7 +27,25 @@ class NRReadChapterCatalogModel: NSObject, SmartCodable {
///
@IgnoredKey
var chapterModel: NRReadChapterModel?
var chapterModel: NRReadChapterModel? {
didSet {
chapterModel?.recommandPage = recommandPage
chapterModel?.finishPage = finishPage
}
}
@IgnoredKey
var recommandPage: NRReadPageModel? {
didSet {
chapterModel?.recommandPage = recommandPage
}
}
@IgnoredKey
var finishPage: NRReadPageModel? {
didSet {
chapterModel?.finishPage = finishPage
}
}
///
func parserEmpty() {

View File

@ -41,7 +41,10 @@ class NRReadChapterModel: NSObject, SmartCodable {
@IgnoredKey
var pageCount: Int = 0
@IgnoredKey
var recommandPage: NRReadPageModel?
@IgnoredKey
var finishPage: NRReadPageModel?
static func mappingForKey() -> [SmartKeyTransformer]? {
@ -52,23 +55,32 @@ class NRReadChapterModel: NSObject, SmartCodable {
///
func parser() {
let tempAttributes = NRNovelReadSetManager.manager.attributes(isTitle: false, isPageing: true)
let tempAttributes = NRNovelReadSetManager.manager.textAttributes()
guard !NSDictionary(dictionary: attributes).isEqual(to: tempAttributes) else { return }
attributes = tempAttributes
fullContent = fullContentAttrString()
var tempPageModel: NRReadPageModel?
if pageList?.last?.pageType != .textPart {
tempPageModel = pageList?.last
}
pageList = NRReadParser.pageing(attrString: fullContent, rect: NRNovelReadSetManager.manager.readRect)
pageCount = pageList?.count ?? 0
if let model = tempPageModel {
pageList?.append(model)
if let pageModel = self.recommandPage {
let pagePoint = pageModel.pagePoint ?? 0
let page = Int(CGFloat(pageCount) * pagePoint)
pageList?.insert(pageModel, at: page)
}
if let pageModel = self.finishPage {
pageList?.append(pageModel)
}
}
///
func parserText(attributes: [NSAttributedString.Key : Any] = NRNovelReadSetManager.manager.textAttributes()) {
self.fullContent = NSMutableAttributedString(string: novel_txt ?? "", attributes: attributes)
pageList = NRReadParser.pageing(attrString: fullContent, rect: NRNovelReadSetManager.manager.readRect)
}
///
@ -86,9 +98,9 @@ class NRReadChapterModel: NSObject, SmartCodable {
///
private func fullContentAttrString() ->NSMutableAttributedString {
let name = "\n\(self.name ?? "")\n\n"
let titleString = NSMutableAttributedString(string: name, attributes: NRNovelReadSetManager.manager.attributes(isTitle: true))
let titleString = NSMutableAttributedString(string: name, attributes: NRNovelReadSetManager.manager.titleAttributes())
let contentString = NSMutableAttributedString(string: novel_txt ?? "", attributes: NRNovelReadSetManager.manager.attributes(isTitle: false))
let contentString = NSMutableAttributedString(string: novel_txt ?? "", attributes: NRNovelReadSetManager.manager.textAttributes())
titleString.append(contentString)

View File

@ -12,6 +12,7 @@ class NRReadPageModel: NSObject {
enum PageType {
case textPart
case readFinish
case recommend
}
var pageType: PageType = .textPart
@ -27,11 +28,20 @@ class NRReadPageModel: NSObject {
var indexPath: IndexPath = IndexPath(row: 0, section: 0)
///
///
// var chapterIndex: Int?
///
///0-1
var pagePoint: CGFloat?
var pageTitle: String?
var recommendList: [NRNovelModel]?
// MARK:
/// ()
var showContent: NSAttributedString {
let textColor = NRNovelReadSetManager.manager.textColor
func showContent(textColor: UIColor = NRNovelReadSetManager.manager.textColor) -> NSAttributedString {
let tempShowContent = NSMutableAttributedString(attributedString: content)
tempShowContent.addAttributes([.foregroundColor : textColor], range: NSMakeRange(0, content.length))
return tempShowContent

View File

@ -47,7 +47,7 @@ class NRNovelReadContentBottomView: UIView {
return label
}()
private lazy var pageLabel: UILabel = {
private(set) lazy var pageLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .regular)
label.textColor = .black

View File

@ -0,0 +1,85 @@
//
// NRNovelReadFinishFooterView.swift
// ReaderHive
//
// Created by on 2026/2/5.
//
import UIKit
import SnapKit
import YYCategories
class NRNovelReadFinishFooterView: UIView {
var clickRefresh: (() -> Void)?
var clickRead: (() -> Void)?
private lazy var refreshButton: UIButton = {
var configuration = UIButton.Configuration.plain()
configuration.background.cornerRadius = 24
configuration.background.backgroundColor = .white
configuration.image = UIImage(named: "refresh_icon_02")
configuration.imagePadding = 8
configuration.attributedTitle = AttributedString("reader_shuffle".localized, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 14, weight: .medium),
.foregroundColor : UIColor.F_9710_D
]))
let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in
self?.clickRefresh?()
}))
return button
}()
private lazy var readButton: UIButton = {
var configuration = UIButton.Configuration.plain()
configuration.background.cornerRadius = 24
configuration.background.image = UIImage(named: "gradient_color_01")
configuration.attributedTitle = AttributedString("reader_continue_reading".localized, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 14, weight: .medium),
.foregroundColor : UIColor.white
]))
let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in
self?.clickRead?()
}))
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
nr_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension NRNovelReadFinishFooterView {
private func nr_setupUI() {
addSubview(refreshButton)
addSubview(readButton)
refreshButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.top.equalToSuperview().offset(8)
make.height.equalTo(48)
make.width.equalTo(120)
make.bottom.equalTo(-(UIScreen.safeBottom + 10))
}
readButton.snp.makeConstraints { make in
make.left.equalTo(refreshButton.snp.right).offset(12)
make.right.equalToSuperview().offset(-16)
make.centerY.height.equalTo(refreshButton)
}
}
}

View File

@ -9,51 +9,30 @@ import UIKit
import SnapKit
import YYCategories
class NRNovelReadFinishHeaderView: UICollectionReusableView {
class NRNovelReadFinishHeaderView: UIView {
var novelModel: NRNovelModel? {
didSet {
coverImageView.nr_setImage(novelModel?.image_url)
// coverImageView.nr_setImage(novelModel?.image_url)
gradeView.model = novelModel
}
}
var recommendNovelModel: NRNovelModel? {
didSet {
self.recommendInfoView.novelModel = recommendNovelModel
self.recommendNovelNameLabel.text = recommendNovelModel?.chapterInfo?.name
}
}
var didChangeContentHeight: ((_ height: CGFloat) -> Void)?
var clickRefresh: (() -> 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()
view.colors = [UIColor.FFC_64_A.cgColor, UIColor.FFF_7_BE.cgColor, UIColor.FFC_982.cgColor]
view.startPoint = .init(x: 0, y: 0.5)
view.endPoint = .init(x: 1, y: 0.5)
view.layer.cornerRadius = 8
view.layer.masksToBounds = true
return view
}()
private lazy var coverImageView: NRImageView = {
let imageView = NRImageView()
imageView.layer.cornerRadius = 7
return imageView
}()
private lazy var coverGradientLayer: CAGradientLayer = {
let layer = CAGradientLayer()
layer.colors = [UIColor.black.cgColor, UIColor.clear.cgColor]
layer.startPoint = CGPoint(x: 0.5, y: 0.4)
layer.endPoint = CGPoint(x: 0.5, y: 1.0)
layer.cornerRadius = 8
return layer
}()
private lazy var coverDecorateView: UIView = {
let imageView = UIImageView(image: UIImage(named: "decorate_image_01"))
return imageView
private lazy var scrollView: NRScrollView = {
let scrollView = NRScrollView()
scrollView.addObserver(self, forKeyPath: "contentSize", context: nil)
return scrollView
}()
private lazy var titleLabel: NRLabel = {
@ -80,6 +59,9 @@ class NRNovelReadFinishHeaderView: UICollectionReusableView {
private lazy var gradeView: NRNovelReadStarGradeView = {
let view = NRNovelReadStarGradeView()
view.backgroundColor = .white
view.layer.borderColor = UIColor.black.withAlphaComponent(0.05).cgColor
view.layer.borderWidth = 1
return view
}()
@ -89,16 +71,24 @@ class NRNovelReadFinishHeaderView: UICollectionReusableView {
return view
}()
lazy var listTitleLabel: UILabel = {
private lazy var recommendInfoView: NRNovelReadFinishInfoView = {
let view = NRNovelReadFinishInfoView()
view.clickRefresh = { [weak self] in
guard let self = self else { return }
self.clickRefresh?()
}
return view
}()
private lazy var recommendNovelNameLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 16, weight: .semibold)
label.textColor = .black
label.text = "reader_book_finished_list_title".localized
return label
}()
deinit {
self.contentView.removeObserver(self, forKeyPath: "contentSize")
self.scrollView.removeObserver(self, forKeyPath: "contentSize")
}
override init(frame: CGRect) {
@ -108,8 +98,7 @@ class NRNovelReadFinishHeaderView: UICollectionReusableView {
override func layoutSubviews() {
super.layoutSubviews()
coverGradientLayer.frame = coverBgView.bounds
coverBgView.layer.mask = coverGradientLayer
}
required init?(coder: NSCoder) {
@ -118,8 +107,7 @@ class NRNovelReadFinishHeaderView: UICollectionReusableView {
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)
self.didChangeContentHeight?(self.scrollView.contentSize.height)
}
}
@ -128,70 +116,54 @@ class NRNovelReadFinishHeaderView: UICollectionReusableView {
extension NRNovelReadFinishHeaderView {
private func nr_setupUI() {
addSubview(contentView)
contentView.addSubview(coverBgView)
coverBgView.addSubview(coverImageView)
contentView.addSubview(coverDecorateView)
contentView.addSubview(titleLabel)
contentView.addSubview(textLabel)
contentView.addSubview(gradeView)
contentView.addSubview(lineView)
contentView.addSubview(listTitleLabel)
addSubview(scrollView)
scrollView.addSubview(titleLabel)
scrollView.addSubview(textLabel)
scrollView.addSubview(gradeView)
scrollView.addSubview(lineView)
scrollView.addSubview(recommendInfoView)
scrollView.addSubview(recommendNovelNameLabel)
contentView.snp.makeConstraints { make in
scrollView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
coverBgView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(16)
make.width.equalTo(100)
make.height.equalTo(150)
}
coverImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(2)
make.top.equalToSuperview().offset(2)
make.center.equalToSuperview()
}
coverDecorateView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(coverBgView).offset(12)
}
titleLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(coverBgView.snp.bottom).offset(20)
// make.right.lessThanOrEqualToSuperview().offset(-16)
make.top.equalToSuperview().offset(2)
make.right.lessThanOrEqualTo(gradeView)
}
textLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(6)
make.top.equalTo(titleLabel.snp.bottom).offset(5)
make.right.lessThanOrEqualTo(gradeView)
}
gradeView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.centerX.equalToSuperview()
make.top.equalTo(textLabel.snp.bottom).offset(25)
make.top.equalTo(textLabel.snp.bottom).offset(20)
}
lineView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.centerX.equalToSuperview()
make.top.equalTo(gradeView.snp.bottom).offset(24)
make.top.equalTo(gradeView.snp.bottom).offset(20)
make.height.equalTo(1)
}
listTitleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.top.equalTo(lineView.snp.bottom).offset(16)
make.bottom.equalToSuperview().offset(-16)
recommendInfoView.snp.makeConstraints { make in
make.left.centerX.equalToSuperview()
make.top.equalTo(lineView.snp.bottom)
}
recommendNovelNameLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.right.lessThanOrEqualTo(recommendInfoView).offset(-16)
make.top.equalTo(recommendInfoView.snp.bottom).offset(12)
make.bottom.equalToSuperview().offset(-8)
}
}
}

View File

@ -0,0 +1,168 @@
//
// NRNovelReadFinishInfoView.swift
// ReaderHive
//
// Created by on 2026/2/3.
//
import UIKit
import SnapKit
class NRNovelReadFinishInfoView: UIView {
var clickRefresh: (() -> Void)?
var novelModel: NRNovelModel? {
didSet {
coverImageView.nr_setImage(novelModel?.image_url)
let rate = novelModel?.rate ?? 0
nameLabel.text = novelModel?.name
starView.grade = rate / 2
starView.text = NSNumber(value: Float(rate)).toString(maximumFractionDigits: 1, minimumFractionDigits: 1)
hotView.setNeedsUpdateConfiguration()
tagStackView.nr_removeAllArrangedSubview()
tagStackView.addArrangedSubview(hotView)
novelModel?.category?.forEach {
guard let view = createCategoryView($0) else { return }
tagStackView.addArrangedSubview(view)
}
}
}
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 16, weight: .semibold)
label.textColor = .black
label.text = "reader_book_more".localized
return label
}()
private lazy var refreshButton: UIButton = {
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
self.clickRefresh?()
}))
button.setImage(UIImage(named: "refresh_icon_01"), for: .normal)
return button
}()
private lazy var coverImageView: NRImageView = {
let imageView = NRImageView()
imageView.layer.cornerRadius = 4
return imageView
}()
private lazy var nameLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 14, weight: .medium)
label.textColor = .black
return label
}()
private lazy var starView: NRStarGradeView = {
let view = NRStarGradeView()
return view
}()
private lazy var tagStackView: UIStackView = {
let view = UIStackView()
view.axis = .horizontal
view.spacing = 8
return view
}()
private lazy var hotView: UIButton = {
var configuration = UIButton.Configuration.plain()
configuration.image = UIImage(named: "hot_icon_01")
configuration.imagePadding = 1
configuration.contentInsets = .init(top: 0, leading: 8, bottom: 0, trailing: 8)
configuration.background.backgroundColor = .black.withAlphaComponent(0.05)
let button = UIButton(configuration: configuration)
button.isUserInteractionEnabled = false
button.layer.cornerRadius = 10
button.layer.masksToBounds = true
button.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
let num = CGFloat(novelModel?.heats ?? 0)
let text = NSNumber(value: num).formattedNumber()
button.configuration?.attributedTitle = AttributedString(text, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 10, weight: .regular),
.foregroundColor : UIColor.black.withAlphaComponent(0.5)
]))
}
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
tagStackView.addArrangedSubview(hotView)
nr_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func createCategoryView(_ text: String?) -> UIView? {
guard let text = text, text.count > 0 else { return nil }
let view = NRHomeCategoryTagView()
view.text = text
return view
}
}
extension NRNovelReadFinishInfoView {
private func nr_setupUI() {
addSubview(titleLabel)
addSubview(refreshButton)
addSubview(coverImageView)
addSubview(nameLabel)
addSubview(starView)
addSubview(tagStackView)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.top.equalToSuperview().offset(12)
}
refreshButton.snp.makeConstraints { make in
make.centerY.equalTo(titleLabel)
make.right.equalToSuperview().offset(-16)
}
coverImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.top.equalTo(titleLabel.snp.bottom).offset(12)
make.width.equalTo(48)
make.height.equalTo(72)
make.bottom.equalToSuperview()
}
nameLabel.snp.makeConstraints { make in
make.left.equalTo(coverImageView.snp.right).offset(12)
make.top.equalTo(coverImageView).offset(0)
make.right.lessThanOrEqualToSuperview().offset(-16)
}
starView.snp.makeConstraints { make in
make.left.equalTo(nameLabel)
make.top.equalTo(nameLabel.snp.bottom).offset(9)
}
tagStackView.snp.makeConstraints { make in
make.bottom.equalTo(coverImageView)
make.left.equalTo(nameLabel)
make.height.equalTo(20)
}
}
}

View File

@ -0,0 +1,50 @@
//
// NRNovelReadFinishTextCell.swift
// ReaderHive
//
// Created by on 2026/2/5.
//
import UIKit
import SnapKit
class NRNovelReadFinishTextCell: NRTableViewCell {
var pageModel: NRReadPageModel? {
didSet {
label.attributedText = pageModel?.content
}
}
private lazy var label: UILabel = {
let label = UILabel()
label.textColor = ._666666
label.numberOfLines = 0
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
nr_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension NRNovelReadFinishTextCell {
private func nr_setupUI() {
contentView.addSubview(label)
label.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.right.lessThanOrEqualToSuperview().offset(-16)
make.top.bottom.equalToSuperview()
}
}
}

View File

@ -0,0 +1,176 @@
//
// NRNovelReadRecommendCell.swift
// ReaderHive
//
// Created by on 2026/2/4.
//
import UIKit
import SnapKit
class NRNovelReadRecommendCell: UICollectionViewCell {
var clickRead: ((_ model: NRNovelModel) -> Void)?
var novelModel: NRNovelModel? {
didSet {
coverImageView.nr_setImage(novelModel?.image_url)
nameLabel.text = novelModel?.name
categoryView.text = novelModel?.category?.first
desLabel.text = novelModel?.nr_description
let star = novelModel?.rate ?? 0
starView.grade = star / 2
starView.text = NSNumber(value: star).toString(maximumFractionDigits: 1, minimumFractionDigits: 1)
}
}
private lazy var coverImageView: NRImageView = {
let imageView = NRImageView()
imageView.layer.cornerRadius = 4
return imageView
}()
private lazy var nameLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 14, weight: .medium)
return label
}()
private lazy var categoryView: NRHomeCategoryTagView = {
let view = NRHomeCategoryTagView()
return view
}()
private lazy var starView: NRStarGradeView = {
let view = NRStarGradeView()
return view
}()
private lazy var desLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 10, weight: .regular)
label.numberOfLines = 2
return label
}()
private lazy var readView: UILabel = {
let label = UILabel()
label.text = "reader_read_now".localized
label.font = .font(ofSize: 14, weight: .medium)
label.layer.cornerRadius = 4
label.layer.masksToBounds = true
label.textAlignment = .center
return label
}()
private lazy var button: UIButton = {
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
guard let model = self.novelModel else { return }
self.clickRead?(model)
}))
return button
}()
deinit {
NotificationCenter.default.removeObserver(self)
}
override init(frame: CGRect) {
super.init(frame: frame)
NotificationCenter.default.addObserver(self, selector: #selector(didChangedThemeNotification), name: NRNovelReadSetManager.didChangedThemeNotification, object: nil)
self.contentView.layer.cornerRadius = 8
self.contentView.layer.masksToBounds = true
self.contentView.layer.borderWidth = 1
didChangedThemeNotification()
nr_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func didChangedThemeNotification() {
if NRNovelReadSetManager.manager.isNight {
self.contentView.layer.borderColor = UIColor.white.withAlphaComponent(0.15).cgColor
self.contentView.backgroundColor = .black.withAlphaComponent(0.5)
self.nameLabel.textColor = .white
self.readView.textColor = .FFEFD_4
self.readView.backgroundColor = .F_9710_D
self.desLabel.textColor = .white.withAlphaComponent(0.5)
self.starView.textColor = .white
self.categoryView.backgroundColor = .white.withAlphaComponent(0.15)
self.categoryView.categoryLabel.textColor = .white.withAlphaComponent(0.5)
} else {
self.contentView.layer.borderColor = UIColor.black.withAlphaComponent(0.05).cgColor
self.contentView.backgroundColor = .white.withAlphaComponent(0.5)
self.nameLabel.textColor = .black
self.readView.textColor = .F_9710_D
self.readView.backgroundColor = .FFEFD_4
self.desLabel.textColor = .black.withAlphaComponent(0.5)
self.starView.textColor = .black
self.categoryView.backgroundColor = .black.withAlphaComponent(0.05)
self.categoryView.categoryLabel.textColor = .black.withAlphaComponent(0.5)
}
}
}
extension NRNovelReadRecommendCell {
private func nr_setupUI() {
contentView.addSubview(coverImageView)
contentView.addSubview(nameLabel)
contentView.addSubview(readView)
contentView.addSubview(categoryView)
contentView.addSubview(starView)
contentView.addSubview(desLabel)
contentView.addSubview(button)
coverImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(8)
make.top.equalToSuperview().offset(8)
make.bottom.equalToSuperview().offset(-8)
make.width.equalTo(80)
}
nameLabel.snp.makeConstraints { make in
make.left.equalTo(coverImageView.snp.right).offset(12)
make.right.lessThanOrEqualToSuperview().offset(-8)
make.top.equalTo(coverImageView)
}
categoryView.snp.makeConstraints { make in
make.left.equalTo(nameLabel)
make.top.equalTo(nameLabel.snp.bottom).offset(8)
}
starView.snp.makeConstraints { make in
make.centerY.equalTo(categoryView)
make.left.equalTo(categoryView.snp.right).offset(8)
}
desLabel.snp.makeConstraints { make in
make.left.equalTo(nameLabel)
make.right.lessThanOrEqualToSuperview().offset(-8)
make.top.equalTo(categoryView.snp.bottom).offset(8)
}
readView.snp.makeConstraints { make in
make.left.equalTo(nameLabel)
make.right.equalToSuperview().offset(-8)
make.bottom.equalTo(coverImageView)
make.height.equalTo(24)
}
button.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}

View File

@ -45,13 +45,13 @@ class NRNovelReadContentViewController: NRNovelReadBaseViewController {
bottomView.catalogModel = self.catalogModel
bottomView.pageModel = self.pageModel
readView.content = pageModel?.showContent
readView.content = pageModel?.showContent()
nr_setupUI()
}
@objc private func didChangedThemeNotification() {
readView.content = pageModel?.showContent
readView.content = pageModel?.showContent()
}
}

View File

@ -7,15 +7,12 @@
import UIKit
import SnapKit
import YYText
class NRNovelReadFinishViewController: NRNovelReadBaseViewController {
lazy var dataArr: [NRNovelModel] = [] {
didSet {
self.collectionView.reloadData()
}
}
private var recommandNovelModel: NRNovelModel?
private lazy var backButton: UIButton = {
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
@ -27,34 +24,55 @@ class NRNovelReadFinishViewController: NRNovelReadBaseViewController {
return button
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let itemWidth = (UIScreen.width - 32 - 40) / 3
let itemHeight = 150 / 100 * itemWidth + 68
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: itemWidth, height: itemHeight)
layout.minimumLineSpacing = 18
layout.minimumInteritemSpacing = 20
layout.sectionInset = .init(top: 0, left: 16, bottom: 0, right: 16)
layout.headerReferenceSize = .init(width: UIScreen.width, height: 390)
return layout
private lazy var tableView: NRTableView = {
let tableView = NRTableView(frame: .zero, style: .plain)
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
tableView.register(NRNovelReadFinishTextCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
private lazy var collectionView: NRCollectionView = {
let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.contentInset = .init(top: 0, left: 0, bottom: UIScreen.safeBottom + 10, right: 0)
collectionView.register(NRNovelDetailMoreLikeCell.self, forCellWithReuseIdentifier: "cell")
collectionView.register(NRNovelReadFinishHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header")
return collectionView
private lazy var headerView: NRNovelReadFinishHeaderView = {
let view = NRNovelReadFinishHeaderView()
view.frame = .init(x: 0, y: 0, width: UIScreen.width, height: 1)
view.novelModel = self.viewModel?.novelModel
view.didChangeContentHeight = { [weak self] height in
guard let self = self else { return }
self.tableView.tableHeaderView = nil
self.headerView.frame = .init(x: 0, y: 0, width: UIScreen.width, height: height)
self.tableView.tableHeaderView = self.headerView
}
view.clickRefresh = { [weak self] in
self?.handleRefresh()
}
return view
}()
private lazy var footerView: NRNovelReadFinishFooterView = {
let view = NRNovelReadFinishFooterView(frame: .init(x: 0, y: 0, width: UIScreen.width, height: 68 + UIScreen.safeBottom))
view.clickRefresh = { [weak self] in
self?.handleRefresh()
}
view.clickRead = { [weak self] in
guard let self = self else { return }
guard let id = self.recommandNovelModel?.id else { return }
let vc = NRNovelReaderViewController()
vc.novelId = id
vc.targetIndex = 1
self.navigationController?.pushViewController(vc, animated: true)
}
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
backgroundImageView.image = UIImage(named: "bg_image_04")
self.view.backgroundColor = .white
self.view.backgroundColor = .F_2_EFEE
// let tap = UITapGestureRecognizer { _ in
//
@ -79,14 +97,42 @@ class NRNovelReadFinishViewController: NRNovelReadBaseViewController {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .darkContent
}
private func handleRefresh() {
Task {
guard await requestDataArr() else { return }
CATransaction.setCompletionBlock {
self.tableView.setContentOffset(.zero, animated: true)
}
CATransaction.begin()
self.tableView.reloadData()
CATransaction.commit()
}
}
}
extension NRNovelReadFinishViewController {
private func nr_setupUI() {
self.tableView.tableHeaderView = self.headerView
self.tableView.tableFooterView = self.footerView
view.addSubview(backButton)
view.addSubview(collectionView)
view.addSubview(tableView)
// scrollView.addSubview(headerView)
// scrollView.addSubview(infoView)
// scrollView.addSubview(novelNameLabel)
// scrollView.addSubview(novelTextLabel)
// scrollView.addSubview(refreshButton)
// scrollView.addSubview(readButton)
// backgroundImageView.snp.remakeConstraints { make in
// make.left.right.equalToSuperview()
// make.top.equalTo(UIScreen.safeTop)
// }
backButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(6)
@ -95,54 +141,81 @@ extension NRNovelReadFinishViewController {
make.width.equalTo(36)
}
collectionView.snp.makeConstraints { make in
tableView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalToSuperview().offset(UIScreen.navBarHeight)
make.top.equalTo(backButton.snp.bottom)
make.bottom.equalToSuperview()
}
// refreshButton.snp.makeConstraints { make in
// make.left.equalToSuperview().offset(16)
// make.top.equalTo(novelTextLabel.snp.bottom).offset(8)
// make.height.equalTo(48)
// make.width.equalTo(120)
// make.bottom.equalTo(-(UIScreen.safeBottom + 10))
// }
// readButton.snp.makeConstraints { make in
// make.left.equalTo(refreshButton.snp.right).offset(12)
// make.right.equalTo(infoView).offset(-16)
// make.centerY.height.equalTo(refreshButton)
// }
}
}
//MARK: UICollectionViewDelegate UICollectionViewDataSource
extension NRNovelReadFinishViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRNovelDetailMoreLikeCell
cell.model = self.dataArr[indexPath.row]
//MARK: UITableViewDelegate & UITableViewDataSource
extension NRNovelReadFinishViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! NRNovelReadFinishTextCell
cell.pageModel = self.recommandNovelModel?.chapterInfo?.pageList?[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.dataArr.count
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.recommandNovelModel?.chapterInfo?.pageList?.count ?? 0
}
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
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let model = dataArr[indexPath.row]
let vc = NRNovelDetailViewController()
vc.novelId = model.id ?? ""
self.viewModel?.vc?.navigationController?.pushViewController(vc, animated: true)
}
}
extension NRNovelReadFinishViewController {
private func requestDataArr() async {
guard let list = await NRNovelAPI.requestDetailRecommandData() else { return }
self.dataArr = list
self.collectionView.reloadData()
private func requestDataArr() async -> Bool {
// guard let list = await NRNovelAPI.requestDetailRecommandData() else { return }
// self.dataArr = list
// self.collectionView.reloadData()
guard let id = self.viewModel?.novelId else { return false }
guard let model = await NRNovelAPI.requestReadFinishRecommand(novelId: id) else { return false }
self.recommandNovelModel = model
let chapterModel = model.chapterInfo
chapterModel?.parserText(attributes: [
.font : UIFont.font(ofSize: 16, weight: .regular),
.foregroundColor : UIColor._666666
])
self.headerView.recommendNovelModel = model
self.tableView.reloadData()
// let text = NSAttributedString(string: chapterModel?.novel_txt ?? "", attributes: [
// .font : UIFont.font(ofSize: 16, weight: .regular),
// .foregroundColor : UIColor._666666
// ])
// let textContainer = YYTextContainer(size: .init(width: UIScreen.width - 32, height: CGFloat(MAXFLOAT)))
// let textLayout = YYTextLayout(container: textContainer, text: text)
//
// self.novelTextLabel.textLayout = textLayout
//
// self.novelTextLabel.snp.updateConstraints { make in
// make.height.equalTo(textLayout?.textBoundingSize.height ?? 0)
// }
return true
}
}

View File

@ -0,0 +1,179 @@
//
// NRNovelReadRecommendViewController.swift
// ReaderHive
//
// Created by on 2026/2/3.
//
import UIKit
import SnapKit
class NRNovelReadRecommendViewController: NRNovelReadBaseViewController {
lazy var topView: NRNovelReadContentTopView = {
let view = NRNovelReadContentTopView()
return view
}()
lazy var bottomView: NRNovelReadContentBottomView = {
let view = NRNovelReadContentBottomView()
view.pageLabel.isHidden = true
return view
}()
lazy var containerView: UIView = {
let view = UIView()
return view
}()
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = .init(width: UIScreen.width - 32, height: 136)
layout.minimumLineSpacing = 16
layout.minimumInteritemSpacing = 16
return layout
}()
private lazy var collectionView: NRCollectionView = {
let collectionView = NRCollectionView(frame: .zero, collectionViewLayout: self.collectionViewLayout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.showsVerticalScrollIndicator = false
collectionView.register(NRNovelReadRecommendCell.self, forCellWithReuseIdentifier: "cell")
return collectionView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 16, weight: .bold)
return label
}()
private lazy var continueButton: UIButton = {
var configuration = UIButton.Configuration.plain()
configuration.imagePadding = 12
configuration.imagePlacement = .trailing
configuration.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: 0)
let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in
}))
button.isUserInteractionEnabled = false
button.configurationUpdateHandler = { [weak self] button in
var textColor: UIColor
if NRNovelReadSetManager.manager.isNight {
button.configuration?.image = UIImage(named: "arrow_right_icon_08")
textColor = .white.withAlphaComponent(0.25)
} else {
button.configuration?.image = UIImage(named: "arrow_right_icon_09")
textColor = .black.withAlphaComponent(0.15)
}
button.configuration?.attributedTitle = AttributedString("reader_swipe_reading".localized, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 18, weight: .medium),
.foregroundColor : textColor
]))
}
return button
}()
@MainActor deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewDidLoad() {
super.viewDidLoad()
self.backgroundImageView.isHidden = true
self.view.backgroundColor = .clear
NotificationCenter.default.addObserver(self, selector: #selector(didChangedThemeNotification), name: NRNovelReadSetManager.didChangedThemeNotification, object: nil)
topView.viewModel = self.viewModel
topView.catalogModel = self.catalogModel
topView.pageModel = self.pageModel
bottomView.viewModel = self.viewModel
bottomView.catalogModel = self.catalogModel
bottomView.pageModel = self.pageModel
self.titleLabel.text = self.pageModel?.pageTitle
nr_setupUI()
didChangedThemeNotification()
}
@objc private func didChangedThemeNotification() {
if NRNovelReadSetManager.manager.isNight {
titleLabel.textColor = .white
} else {
titleLabel.textColor = .black
}
continueButton.setNeedsUpdateConfiguration()
}
}
extension NRNovelReadRecommendViewController {
private func nr_setupUI() {
view.addSubview(topView)
view.addSubview(bottomView)
view.addSubview(containerView)
containerView.addSubview(collectionView)
containerView.addSubview(titleLabel)
containerView.addSubview(continueButton)
topView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
}
bottomView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
}
containerView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalTo(topView.snp.bottom)
make.bottom.equalTo(bottomView.snp.top)
}
collectionView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.centerY.equalToSuperview().offset(-25)
make.height.equalTo(collectionViewLayout.itemSize.height * 3 + collectionViewLayout.minimumLineSpacing * 2)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.bottom.equalTo(collectionView.snp.top).offset(-16)
}
continueButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(collectionView.snp.bottom).offset(35)
}
}
}
//MARK: UICollectionViewDelegate UICollectionViewDataSource
extension NRNovelReadRecommendViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! NRNovelReadRecommendCell
cell.novelModel = self.pageModel?.recommendList?[indexPath.row]
cell.clickRead = { [weak self] model in
guard let self = self else { return }
let vc = NRNovelReaderViewController()
vc.novelId = model.id ?? "0"
self.navigationController?.pushViewController(vc, animated: true)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.pageModel?.recommendList?.count ?? 0
}
}

View File

@ -168,7 +168,9 @@ extension NRNovelReaderViewController {
guard let catalogModel = catalogModel, let pageModel = pageModel else { return nil }
let vc: NRNovelReadBaseViewController
if pageModel.pageType == .readFinish {
vc = NRNovelReadFinishViewController()
vc = self.finishVC
} else if pageModel.pageType == .recommend {
vc = NRNovelReadRecommendViewController()
} else {
vc = NRNovelReadContentViewController()
}

View File

@ -39,6 +39,15 @@ class NRNovelReaderViewController: NRViewController {
}
}
var targetIndex: Int? {
set {
self.viewModel.targetIndex = newValue
}
get {
return self.viewModel.targetIndex
}
}
var oldBrightness: CGFloat = UIScreen.main.brightness
private(set) lazy var viewModel: NRNovelReadViewModel = {
@ -70,6 +79,11 @@ class NRNovelReaderViewController: NRViewController {
return vc
}()
private(set) lazy var finishVC: NRNovelReadFinishViewController = {
let vc = NRNovelReadFinishViewController()
return vc
}()
private lazy var readContentView: UIView = {
let view = UIView()
return view
@ -142,18 +156,18 @@ class NRNovelReaderViewController: NRViewController {
}
override var childForStatusBarHidden: UIViewController? {
if let _ = self.pageViewController.viewControllers?.first as? NRNovelReadContentViewController {
return nil
if let vc = self.pageViewController.viewControllers?.first as? NRNovelReadFinishViewController {
return vc
} else {
return self.pageViewController.viewControllers?.first
return nil
}
}
override var childForStatusBarStyle: UIViewController? {
if let _ = self.pageViewController.viewControllers?.first as? NRNovelReadContentViewController {
return nil
if let vc = self.pageViewController.viewControllers?.first as? NRNovelReadFinishViewController {
return vc
} else {
return self.pageViewController.viewControllers?.first
return nil
}
}
@ -187,6 +201,7 @@ extension NRNovelReaderViewController {
return;
}
self.view.ly_hideEmpty()
addChild(self.pageViewController)
view.addSubview(readContentView)
view.addSubview(topView)
@ -239,6 +254,8 @@ extension NRNovelReaderViewController {
let currentCatalogModel = self.viewModel.chapterCatalogList[indexPath.section]
await self.viewModel.requestReadRecommand(type: 1)
await self.viewModel.requestReadRecommand(type: 2)
//
await self.viewModel.requestChapterData(currentCatalogModel)
@ -260,7 +277,11 @@ extension NRNovelReaderViewController {
//
var section = 0
var row = 0
if let catalogModel = self.targetCatalogModel {
if let targetIndex = self.targetIndex {
section = targetIndex
self.targetIndex = nil
} else if let catalogModel = self.targetCatalogModel {
for (index, model) in self.viewModel.chapterCatalogList.enumerated() {
if model.id == catalogModel.id {
section = index

View File

@ -105,10 +105,19 @@ extension NRNovelReadViewModel {
currentRow = listCount - 1
}
if haveText {
let currentPage = catalogModel.chapterModel?.pageList?[currentRow]
if currentPage?.pageType != .textPart {
var currentPage = catalogModel.chapterModel?.pageList?[currentRow]
while currentPage != nil, currentPage?.pageType != .textPart, currentRow > 0 {
currentRow = currentRow - 1
currentPage = catalogModel.chapterModel?.pageList?[currentRow]
}
// if currentPage?.pageType != .textPart {
// currentRow = currentRow - 1
// }
}
self.currentPageIndexPath = IndexPath(row: currentRow, section: currentSection)
@ -218,6 +227,8 @@ extension NRNovelReadViewModel {
func requestChapterCatalogList() async {
guard let list = await NRNovelAPI.requestChapterCatalogList(id: self.novelId) else { return }
await MainActor.run {
let model = list.last
model?.finishPage = NRReadPageModel.createReadFinishModel()
self.chapterCatalogList = list
self.setEmptyData()
}
@ -235,13 +246,9 @@ extension NRNovelReadViewModel {
if code == 200 {
guard let model = model else { return nil }
model.parser()
///
if chapterId == self.chapterCatalogList.last?.id {
model.pageList?.append(NRReadPageModel.createReadFinishModel())
}
catalogModel.chapterModel = model
catalogModel.chapterModel?.parser()
if catalogModel.is_lock == true {
catalogModel.is_lock = false
@ -314,5 +321,27 @@ extension NRNovelReadViewModel {
NRStatAPI.nr_requestWatchNovelDuration(novelId: self.novelId, duration: duration, percent: Int(percent))
}
func requestReadRecommand(type: Int) async {
guard let list = await NRNovelAPI.requestReadRecommand(type: type) else { return }
var index: Int?
let pageModel = NRReadPageModel()
pageModel.pageType = .recommend
pageModel.recommendList = list
if type == 1 {
index = self.chapterCatalogList.count - 2
pageModel.pagePoint = 0.5
pageModel.pageTitle = "reader_book_more".localized
} else {
index = self.chapterCatalogList.count - 1
pageModel.pagePoint = 1
pageModel.pageTitle = "reader_editors_choice".localized
}
if let index = index, index > 0 {
let catalogModel = self.chapterCatalogList[index]
catalogModel.recommandPage = pageModel
}
}
}
//MARK: END

View File

@ -17,6 +17,7 @@ class NRNovelReadViewModel: NSObject {
@objc dynamic var novelModel: NRNovelModel?
///
var targetCatalogModel: NRReadChapterCatalogModel?
var targetIndex: Int?
weak var vc: NRNovelReaderViewController?
weak var topView: NRNovelReadTopView?

View File

@ -135,8 +135,26 @@ class NRNovelReadSetManager: NSObject {
}
///
/// isPaging: YES (:UIColor,...,)
func attributes(isTitle:Bool, isPageing:Bool = false) ->[NSAttributedString.Key:Any] {
func titleAttributes() ->[NSAttributedString.Key : Any] {
//
let paragraphStyle = NSMutableParagraphStyle()
// (lineSpacing)()
paragraphStyle.lineHeightMultiple = 1.0
//
paragraphStyle.lineSpacing = 0
//
paragraphStyle.paragraphSpacing = 0
//
paragraphStyle.alignment = .center
return [.font: self.titleFont, .paragraphStyle: paragraphStyle]
}
func textAttributes() ->[NSAttributedString.Key:Any] {
//
let paragraphStyle = NSMutableParagraphStyle()
@ -144,51 +162,28 @@ class NRNovelReadSetManager: NSObject {
// (lineSpacing)()
paragraphStyle.lineHeightMultiple = 1.0
if isTitle {
//
paragraphStyle.lineSpacing = 0
//
paragraphStyle.paragraphSpacing = 0
//
paragraphStyle.alignment = .center
}else{
//
paragraphStyle.lineSpacing = lineSpacing
//
// paragraphStyle.lineBreakMode = .byCharWrapping
paragraphStyle.lineBreakMode = .byWordWrapping
//
paragraphStyle.paragraphSpacing = paragraphSpacing
//
paragraphStyle.alignment = .justified
}
let font: UIFont
//
paragraphStyle.lineSpacing = lineSpacing
if isTitle {
font = self.titleFont
} else {
font = self.textFont
}
//
// paragraphStyle.lineBreakMode = .byCharWrapping
paragraphStyle.lineBreakMode = .byWordWrapping
if isPageing {
return [.font: font, .paragraphStyle: paragraphStyle]
}else{
return [.foregroundColor: textColor, .font: font, .paragraphStyle: paragraphStyle]
}
//
paragraphStyle.paragraphSpacing = paragraphSpacing
//
paragraphStyle.alignment = .justified
let font: UIFont = self.textFont
return [.font: font, .paragraphStyle: paragraphStyle]
}
func emptyAttributes() -> [NSAttributedString.Key:Any] {
//
let paragraphStyle = NSMutableParagraphStyle()

View File

@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "阅读完成徽章@2x.png",
"filename" : "快进,滑动@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "阅读完成徽章@3x.png",
"filename" : "快进,滑动@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "快进,滑动@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "快进,滑动@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

View File

@ -5,12 +5,12 @@
"scale" : "1x"
},
{
"filename" : "阅读完成页背景@2x.png",
"filename" : "阅读完成页背景2@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "阅读完成页背景@3x.png",
"filename" : "阅读完成页背景2@3x.png",
"idiom" : "universal",
"scale" : "3x"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "刷新@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "刷新@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "刷新@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "刷新@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -153,6 +153,10 @@
"reader_my_vip_tips" = "Unrestricted access to all series!";
"reader_visitor" = "Visitor";
"free_coins" = "free coins";
"reader_shuffle" = "Shuffle";
"reader_continue_reading" = "Continue Reading";
"reader_editors_choice" = "Editor's Choice for You";
"reader_swipe_reading" = "Swipe to continue reading";
"vip_retain_alert_text" = "Unlock every show you love!";
"reader_log_out_content" = "Are you sure you want to log out?";