337 lines
13 KiB
Swift
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)
|
|
}
|
|
}
|
|
|
|
}
|