365 lines
13 KiB
Swift
365 lines
13 KiB
Swift
//
|
|
// VersionUpgradePopVC.swift
|
|
// QuickLocation
|
|
//
|
|
|
|
import UIKit
|
|
|
|
final class VersionUpgradePopVC: UIViewController {
|
|
|
|
var completion: (() -> Void)?
|
|
var dismissHandler: ((VersionUpgradePopVC, @escaping () -> Void) -> Void)?
|
|
|
|
private let upgrade: VersionUpgradeModel
|
|
private var isOpeningURL = false
|
|
private var hasFinished = false
|
|
private var contentTextViewHeightConstraint: NSLayoutConstraint?
|
|
|
|
init(upgrade: VersionUpgradeModel) {
|
|
self.upgrade = upgrade
|
|
super.init(nibName: nil, bundle: nil)
|
|
modalPresentationStyle = .overFullScreen
|
|
modalTransitionStyle = .crossDissolve
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
setupUI()
|
|
configureContent()
|
|
}
|
|
|
|
func showAnimated() {
|
|
view.layoutIfNeeded()
|
|
dimmingView.alpha = 0
|
|
popupContainerView.alpha = 0
|
|
popupContainerView.transform = CGAffineTransform(scaleX: 0.94, y: 0.94)
|
|
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseOut]) {
|
|
self.dimmingView.alpha = 1
|
|
self.popupContainerView.alpha = 1
|
|
self.popupContainerView.transform = .identity
|
|
}
|
|
}
|
|
|
|
private func setupUI() {
|
|
view.backgroundColor = .clear
|
|
|
|
view.addSubview(dimmingView)
|
|
dimmingView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
if !upgrade.force {
|
|
dimmingView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(cancelUpgrade)))
|
|
}
|
|
|
|
view.addSubview(popupContainerView)
|
|
popupContainerView.addSubview(cardView)
|
|
popupContainerView.addSubview(headerImageView)
|
|
popupContainerView.addSubview(cancelButton)
|
|
cardView.addSubview(contentTextView)
|
|
cardView.addSubview(upgradeButton)
|
|
|
|
[popupContainerView, cardView, headerImageView, cancelButton, contentTextView, upgradeButton].forEach {
|
|
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
}
|
|
|
|
let contentTextViewHeightConstraint = contentTextView.heightAnchor.constraint(equalToConstant: 96)
|
|
self.contentTextViewHeightConstraint = contentTextViewHeightConstraint
|
|
|
|
NSLayoutConstraint.activate([
|
|
dimmingView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
dimmingView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
dimmingView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
dimmingView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
|
|
popupContainerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
|
popupContainerView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
|
popupContainerView.widthAnchor.constraint(equalToConstant: 325),
|
|
popupContainerView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 20),
|
|
popupContainerView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -20),
|
|
|
|
headerImageView.topAnchor.constraint(equalTo: popupContainerView.topAnchor),
|
|
headerImageView.leadingAnchor.constraint(equalTo: cardView.leadingAnchor),
|
|
headerImageView.widthAnchor.constraint(equalTo: cardView.widthAnchor, constant: 10),
|
|
headerImageView.heightAnchor.constraint(equalTo: headerImageView.widthAnchor, multiplier: 132 / 325),
|
|
|
|
cardView.topAnchor.constraint(equalTo: popupContainerView.topAnchor, constant: 88),
|
|
cardView.centerXAnchor.constraint(equalTo: popupContainerView.centerXAnchor),
|
|
cardView.widthAnchor.constraint(equalToConstant: 315),
|
|
|
|
contentTextView.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 40),
|
|
contentTextView.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 20),
|
|
contentTextView.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -20),
|
|
contentTextViewHeightConstraint,
|
|
|
|
upgradeButton.topAnchor.constraint(equalTo: contentTextView.bottomAnchor, constant: 28),
|
|
upgradeButton.leadingAnchor.constraint(equalTo: contentTextView.leadingAnchor),
|
|
upgradeButton.trailingAnchor.constraint(equalTo: contentTextView.trailingAnchor),
|
|
upgradeButton.heightAnchor.constraint(equalToConstant: 50),
|
|
upgradeButton.bottomAnchor.constraint(equalTo: cardView.bottomAnchor, constant: -20),
|
|
|
|
cancelButton.topAnchor.constraint(equalTo: cardView.bottomAnchor, constant: 14),
|
|
cancelButton.centerXAnchor.constraint(equalTo: popupContainerView.centerXAnchor),
|
|
cancelButton.heightAnchor.constraint(equalToConstant: 28),
|
|
cancelButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 80),
|
|
cancelButton.bottomAnchor.constraint(equalTo: popupContainerView.bottomAnchor)
|
|
])
|
|
}
|
|
|
|
private func configureContent() {
|
|
cancelButton.isHidden = upgrade.force
|
|
|
|
let title = upgrade.title + upgrade.version
|
|
let description = upgrade.description
|
|
let content = NSMutableAttributedString()
|
|
|
|
if !title.isEmpty {
|
|
let paragraphStyle = NSMutableParagraphStyle()
|
|
paragraphStyle.lineSpacing = 3
|
|
content.append(NSAttributedString(
|
|
string: title,
|
|
attributes: [
|
|
.font: UIFont.systemFont(ofSize: 15, weight: .medium),
|
|
.foregroundColor: UIColor(hexStr: "#273246"),
|
|
.paragraphStyle: paragraphStyle
|
|
]
|
|
))
|
|
}
|
|
|
|
if !description.isEmpty {
|
|
if !content.string.isEmpty {
|
|
content.append(NSAttributedString(string: "\n"))
|
|
}
|
|
let paragraphStyle = NSMutableParagraphStyle()
|
|
paragraphStyle.lineSpacing = 5
|
|
content.append(NSAttributedString(
|
|
string: description,
|
|
attributes: [
|
|
.font: UIFont.systemFont(ofSize: 15, weight: .regular),
|
|
.foregroundColor: UIColor(hexStr: "#273246"),
|
|
.paragraphStyle: paragraphStyle
|
|
]
|
|
))
|
|
}
|
|
|
|
contentTextView.attributedText = content
|
|
contentTextView.setContentOffset(.zero, animated: false)
|
|
let fittingSize = contentTextView.sizeThatFits(
|
|
CGSize(width: 275, height: CGFloat.greatestFiniteMagnitude)
|
|
)
|
|
contentTextViewHeightConstraint?.constant = max(96, ceil(fittingSize.height))
|
|
}
|
|
|
|
@objc private func openUpgradeURL() {
|
|
guard !isOpeningURL else { return }
|
|
let urlString = upgrade.url.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard let url = URL(string: urlString),
|
|
let scheme = url.scheme,
|
|
!scheme.isEmpty else {
|
|
DLToast.show(text: "下载地址不可用")
|
|
return
|
|
}
|
|
|
|
isOpeningURL = true
|
|
UIApplication.shared.open(url, options: [:]) { [weak self] opened in
|
|
DispatchQueue.main.async {
|
|
guard let self else { return }
|
|
self.isOpeningURL = false
|
|
guard opened else {
|
|
DLToast.show(text: "下载地址不可用")
|
|
return
|
|
}
|
|
if !self.upgrade.force {
|
|
self.finishUpgrade()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc private func cancelUpgrade() {
|
|
guard !upgrade.force else { return }
|
|
finishUpgrade()
|
|
}
|
|
|
|
private func finishUpgrade() {
|
|
guard !hasFinished else { return }
|
|
hasFinished = true
|
|
let finish = { [weak self] in
|
|
guard let self else { return }
|
|
let completion = self.completion
|
|
self.completion = nil
|
|
completion?()
|
|
}
|
|
guard let dismissHandler else {
|
|
dismiss(animated: true, completion: finish)
|
|
return
|
|
}
|
|
dismissHandler(self, finish)
|
|
}
|
|
|
|
private lazy var dimmingView: UIView = {
|
|
let view = UIView()
|
|
view.backgroundColor = UIColor.black.withAlphaComponent(0.68)
|
|
return view
|
|
}()
|
|
|
|
private let popupContainerView = UIView()
|
|
|
|
private lazy var cardView: UIView = {
|
|
let cardView = UIView()
|
|
cardView.backgroundColor = .white
|
|
cardView.layer.cornerRadius = 36
|
|
cardView.layer.masksToBounds = true
|
|
return cardView
|
|
}()
|
|
|
|
private lazy var headerImageView: UIImageView = {
|
|
let imageView = UIImageView(image: UIImage(named: "VersionUpgrade/header"))
|
|
imageView.contentMode = .scaleAspectFit
|
|
return imageView
|
|
}()
|
|
|
|
private lazy var contentTextView: UITextView = {
|
|
let textView = UITextView()
|
|
textView.backgroundColor = UIColor(hexStr: "#F0FAFF")
|
|
textView.layer.cornerRadius = 26
|
|
textView.layer.masksToBounds = true
|
|
textView.isEditable = false
|
|
textView.isSelectable = false
|
|
textView.isScrollEnabled = false
|
|
textView.showsVerticalScrollIndicator = false
|
|
textView.textContainerInset = UIEdgeInsets(top: 15, left: 35, bottom: 15, right: 35)
|
|
textView.textContainer.lineFragmentPadding = 0
|
|
return textView
|
|
}()
|
|
|
|
private lazy var upgradeButton: VersionUpgradeGradientButton = {
|
|
let button = VersionUpgradeGradientButton(type: .custom)
|
|
button.setTitle("立即体验", for: .normal)
|
|
button.setTitleColor(.white, for: .normal)
|
|
button.titleLabel?.font = .systemFont(ofSize: 18, weight: .medium)
|
|
button.addTarget(self, action: #selector(openUpgradeURL), for: .touchUpInside)
|
|
return button
|
|
}()
|
|
|
|
private lazy var cancelButton: UIButton = {
|
|
let button = UIButton(type: .custom)
|
|
button.setTitle("取消", for: .normal)
|
|
button.setTitleColor(.white, for: .normal)
|
|
button.titleLabel?.font = .systemFont(ofSize: 17, weight: .regular)
|
|
button.addTarget(self, action: #selector(cancelUpgrade), for: .touchUpInside)
|
|
return button
|
|
}()
|
|
}
|
|
|
|
private final class VersionUpgradeGradientButton: UIButton {
|
|
|
|
private let gradientLayer = CAGradientLayer()
|
|
|
|
override init(frame: CGRect) {
|
|
super.init(frame: frame)
|
|
gradientLayer.colors = [
|
|
UIColor(hexStr: "#08B5FF").cgColor,
|
|
UIColor(hexStr: "#48C7FF").cgColor
|
|
]
|
|
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
|
|
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
|
|
layer.insertSublayer(gradientLayer, at: 0)
|
|
layer.masksToBounds = true
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
gradientLayer.frame = bounds
|
|
layer.cornerRadius = 16
|
|
}
|
|
}
|
|
|
|
extension UIViewController {
|
|
|
|
func showVersionUpgradePop(
|
|
upgrade: VersionUpgradeModel,
|
|
completion: (() -> Void)? = nil
|
|
) {
|
|
VersionUpgradeWindowPresenter.show(
|
|
upgrade: upgrade,
|
|
preferredScene: viewIfLoaded?.window?.windowScene,
|
|
completion: completion
|
|
)
|
|
}
|
|
}
|
|
|
|
private enum VersionUpgradeWindowPresenter {
|
|
|
|
private static var hostWindow: UIWindow?
|
|
private static weak var previousKeyWindow: UIWindow?
|
|
|
|
static func show(
|
|
upgrade: VersionUpgradeModel,
|
|
preferredScene: UIWindowScene?,
|
|
completion: (() -> Void)?
|
|
) {
|
|
let showBlock = {
|
|
guard hostWindow == nil,
|
|
let windowScene = preferredScene ?? activeWindowScene() else { return }
|
|
|
|
previousKeyWindow = windowScene.windows.first { $0.isKeyWindow }
|
|
|
|
let viewController = VersionUpgradePopVC(upgrade: upgrade)
|
|
viewController.completion = completion
|
|
viewController.dismissHandler = { viewController, finish in
|
|
dismiss(viewController, finish: finish)
|
|
}
|
|
|
|
let window = UIWindow(windowScene: windowScene)
|
|
window.frame = windowScene.coordinateSpace.bounds
|
|
window.windowLevel = .alert + 1
|
|
window.backgroundColor = .clear
|
|
window.rootViewController = viewController
|
|
hostWindow = window
|
|
window.makeKeyAndVisible()
|
|
viewController.showAnimated()
|
|
}
|
|
|
|
if Thread.isMainThread {
|
|
showBlock()
|
|
} else {
|
|
DispatchQueue.main.async(execute: showBlock)
|
|
}
|
|
}
|
|
|
|
private static func dismiss(
|
|
_ viewController: VersionUpgradePopVC,
|
|
finish: @escaping () -> Void
|
|
) {
|
|
guard hostWindow?.rootViewController === viewController else {
|
|
finish()
|
|
return
|
|
}
|
|
|
|
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseIn]) {
|
|
viewController.view.alpha = 0
|
|
} completion: { _ in
|
|
hostWindow?.isHidden = true
|
|
hostWindow?.rootViewController = nil
|
|
hostWindow = nil
|
|
previousKeyWindow?.makeKey()
|
|
previousKeyWindow = nil
|
|
finish()
|
|
}
|
|
}
|
|
|
|
private static func activeWindowScene() -> UIWindowScene? {
|
|
let scenes = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
|
|
return scenes.first { $0.activationState == .foregroundActive }
|
|
?? scenes.first { $0.activationState == .foregroundInactive }
|
|
?? scenes.first
|
|
}
|
|
}
|