ReaderHive/ReaderHive/Class/Novel/V/Reader/NRNovelReadBottomView.swift
澜声世纪 17d83f3b7a 修复bug
2025-12-25 14:50:08 +08:00

337 lines
13 KiB
Swift

//
// NRNovelReadBottomView.swift
// ReaderHive
//
// Created by on 2025/12/1.
//
import UIKit
import HWPanModal
import SnapKit
class NRNovelReadBottomView: UIView {
weak var viewModel: NRNovelReadViewModel? {
didSet {
self.viewModel?.addObserver(self, forKeyPath: "novelModel", context: nil)
updateProgress()
}
}
override var intrinsicContentSize: CGSize {
return .init(width: UIScreen.width, height: NRNovelReadSetManager.manager.bottomViewHeight)
}
private var collectAnimate = false
lazy var contentView: UIView = {
let view = UIView()
view.backgroundColor = .white
return view
}()
lazy var catalogButton: UIButton = {
let button = self.createButton(title: "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()
}), for: .touchUpInside)
return button
}()
lazy var nightButton: UIButton = {
let button = self.createButton(title: "Night".localized, nightTitle: "Default".localized, icon: UIImage(named: "night_icon_01"), nightIcon: UIImage(named: "night_icon_02"))
button.addAction(UIAction(handler: { [weak self] _ in
guard let self = self else { return }
let manager = NRNovelReadSetManager.manager
manager.updateIsNight(isNight: !manager.isNight)
}), for: .touchUpInside)
return button
}()
lazy var settingsButton: UIButton = {
let button = self.createButton(title: "Settings".localized, icon: UIImage(named: "settings_icon_01"), nightIcon: UIImage(named: "settings_icon_03"))
button.addAction(UIAction(handler: { [weak self] _ in
guard let self = self else { return }
self.viewModel?.showSettingView()
}), for: .touchUpInside)
return button
}()
lazy var stackView: UIStackView = {
let view = UIStackView(arrangedSubviews: [catalogButton, nightButton, settingsButton])
view.axis = .horizontal
view.distribution = .equalSpacing
return view
}()
lazy var prevButton: UIButton = {
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
self.viewModel?.skipToPrevChapter()
}))
button.titleLabel?.font = .font(ofSize: 12, weight: .regular)
button.setTitle("Prev".localized, for: .normal)
button.setTitleColor(.black, for: .normal)
button.setContentHuggingPriority(.required, for: .horizontal)
button.setContentCompressionResistancePriority(.required, for: .horizontal)
return button
}()
lazy var nextButton: UIButton = {
let button = UIButton(type: .custom, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
guard let (catalogModel, _) = self.viewModel?.getCurrentPageData() else { return }
if catalogModel?.is_lock != true {
self.viewModel?.skipToNextChapter()
} else {
self.viewModel?.openRechargeView()
}
}))
button.titleLabel?.font = .font(ofSize: 12, weight: .regular)
button.setTitle("Next".localized, for: .normal)
button.setTitleColor(.black, for: .normal)
button.setContentHuggingPriority(.required, for: .horizontal)
button.setContentCompressionResistancePriority(.required, for: .horizontal)
return button
}()
lazy var progressView: NRProgressView = {
let view = NRProgressView()
view.isUserInteractionEnabled = false
view.thumbImage = UIImage(named: "Progress-handle")
view.insets = .init(top: 6, left: 0, bottom: 6, right: 0)
view.panFinish = { [weak self] progress in
self?.progressView.progress = progress
let totalCount = self?.viewModel?.chapterCatalogList.count ?? 0
let index = Int(floor(CGFloat(totalCount) * progress))
self?.viewModel?.skip(chapterIndex: index, dismissMenu: false)
}
return view
}()
lazy var collectButton: UIButton = {
var configuration = UIButton.Configuration.plain()
configuration.contentInsets = .init(top: 0, leading: 11, bottom: 0, trailing: 11)
configuration.imagePadding = 4
let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in
self?.handleCollectButton()
}))
button.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
if self.viewModel?.novelModel?.is_collect == true {
button.configuration?.background.backgroundColor = .FFEFD_4
button.configuration?.image = UIImage(named: "done_icon_01")
button.configuration?.attributedTitle = AttributedString("In My List".localized, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 12, weight: .medium),
.foregroundColor : UIColor.F_9710_D
]))
} else {
button.configuration?.background.backgroundColor = .F_9710_D
button.configuration?.image = UIImage(named: "+_icon_01")
button.configuration?.attributedTitle = AttributedString("Add to My List".localized, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 12, weight: .medium),
.foregroundColor : UIColor.white
]))
}
}
button.layer.cornerRadius = 8
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.withAlphaComponent(0.05).cgColor
return button
}()
deinit {
self.viewModel?.removeObserver(self, forKeyPath: "novelModel")
NotificationCenter.default.removeObserver(self)
}
override init(frame: CGRect) {
super.init(frame: frame)
NotificationCenter.default.addObserver(self, selector: #selector(updateCollectStateNotification), name: NRNovelAPI.updateCollectStateNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didChangedThemeNotification), name: NRNovelReadSetManager.didChangedThemeNotification, object: nil)
backgroundColor = .clear
nr_setupUI()
didChangedThemeNotification()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "novelModel" {
updateCollectState()
}
}
@objc private func updateCollectStateNotification(sender: Notification) {
guard !collectAnimate else { return }
guard let userInfo = sender.userInfo else { return }
guard let id = userInfo["id"] as? String else { return }
guard let state = userInfo["state"] as? Bool else { return }
guard id == self.viewModel?.novelId else { return }
self.viewModel?.novelModel?.is_collect = state
updateCollectState()
}
private func updateCollectState() {
collectButton.setNeedsUpdateConfiguration()
collectButton.isHidden = self.viewModel?.novelModel?.is_collect ?? false
}
func updateProgress() {
let progress = (CGFloat(viewModel?.currentPageIndexPath.section ?? 0) + 1) / CGFloat(viewModel?.chapterCatalogList.count ?? 0)
self.progressView.progress = progress
}
// override func backgroundConfig() -> HWBackgroundConfig {
// let config = HWBackgroundConfig()
// config.backgroundAlpha = 0
// return config
// }
private func createButton(title: String, nightTitle: String? = nil, icon: UIImage?, nightIcon: UIImage?) -> UIButton {
var configuration = UIButton.Configuration.plain()
configuration.contentInsets = .zero
configuration.imagePadding = 2
configuration.imagePlacement = .top
let button = UIButton(configuration: configuration)
button.configurationUpdateHandler = { [weak self] button in
guard let _ = self else { return }
let textColor: UIColor
var _title = title
if NRNovelReadSetManager.manager.isNight {
textColor = .white
button.configuration?.image = nightIcon
if let t = nightTitle {
_title = t
}
} else {
textColor = .black
button.configuration?.image = icon
}
button.configuration?.attributedTitle = AttributedString(_title, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 10, weight: .regular),
.foregroundColor : textColor
]))
}
button.snp.makeConstraints { make in
make.width.equalTo(72)
}
return button
}
private func handleCollectButton() {
guard let model = self.viewModel?.novelModel else { return }
guard model.is_collect == false else { return }
guard let (catalogModel, _) = self.viewModel?.getCurrentPageData() else { return }
let isCollect = !(model.is_collect ?? false)
Task {
if await NRNovelAPI.requestCollect(isCollect: isCollect, id: model.id ?? "", chapterId: catalogModel?.id) {
self.collectAnimate = true
self.viewModel?.novelModel?.is_collect = isCollect
self.collectButton.setNeedsUpdateConfiguration()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
guard let self = self else { return }
self.updateCollectState()
self.collectAnimate = false
}
}
}
}
@objc private func didChangedThemeNotification() {
if NRNovelReadSetManager.manager.isNight == true {
prevButton.setTitleColor(.white, for: .normal)
nextButton.setTitleColor(.white, for: .normal)
contentView.backgroundColor = ._36353_A
progressView.progressColor = .white.withAlphaComponent(0.25)
} else {
prevButton.setTitleColor(.black, for: .normal)
nextButton.setTitleColor(.black, for: .normal)
contentView.backgroundColor = .white
progressView.progressColor = .black.withAlphaComponent(0.25)
}
catalogButton.setNeedsUpdateConfiguration()
nightButton.setNeedsUpdateConfiguration()
settingsButton.setNeedsUpdateConfiguration()
}
}
extension NRNovelReadBottomView {
private func nr_setupUI() {
addSubview(collectButton)
addSubview(contentView)
contentView.addSubview(stackView)
contentView.addSubview(progressView)
contentView.addSubview(prevButton)
contentView.addSubview(nextButton)
collectButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview()
make.height.equalTo(36)
}
contentView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(UIScreen.safeBottom + 105)
}
stackView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(28)
make.centerX.equalToSuperview()
make.top.equalToSuperview().offset(48)
make.height.equalTo(56)
}
progressView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.centerX.equalToSuperview()
make.right.lessThanOrEqualTo(nextButton.snp.left).offset(0)
make.left.greaterThanOrEqualTo(prevButton.snp.right).offset(0)
}
prevButton.snp.makeConstraints { make in
make.height.equalTo(40)
make.centerY.equalTo(progressView)
make.left.equalToSuperview().offset(20)
}
nextButton.snp.makeConstraints { make in
make.height.equalTo(40)
make.centerY.equalTo(progressView)
make.right.equalToSuperview().offset(-20)
}
}
}